Asynchronous function in a while-loop

2019-06-24 02:39发布

I have a question about how to perform an asynchronous task in a while-loop until some condition is met. This is more of a theoretical question but I can see how this could be an issue in some scenarios.

I'll try demonstrate the problem at an example (I'm using JavaScript here, but you can use any language):

I could have a device and I want to hold my application until that device has reached a specific state. If the method with which I can getrieve with state of the device is synchronouse, the code could look like this:

// Hold until the desired state is reached
var state = false;
while (!state) {
    state = device.getStateSync();
}
// [...] continue the program

My question now is: How can I transform this code when all I get from the device is a asynchronous getState function? The code should work no matter how long the execution time of the call is and should keep in mind that I'm working on limited memory and stack size.

// [...] This would call the async function in a loop and crash the program
while (!state) {
    // [...] something
    device.getStateAsync(function(newState) {
        state = newState;
    });
    // [...] something else
}

This one post that I found has a recursive solution (http://blog.victorquinn.com/javascript-promise-while-loop). While this is a nice solution, at some point it will run into stack-size problems, if the loop is called too often.

Right now I have the gut feeling that there might be no solution. Do you know of any way to do this? Or do you know how to prove that there is no way to do it? Feel free to include more complex concepts like Threads, Promises or Futures.

Please bear in mind, that this is a theoretical question and the example is for a situation where I have no way of changing the framework (or device) I am working with.

Thanks for every response and idea!

Pedro

5条回答
地球回转人心会变
2楼-- · 2019-06-24 03:26

In javascript, you cannot loop waiting for a condition to change unless the code that actually changes the condition is inside that very loop or a side effect of some function called in the loop. That's because javascript is single threaded (except for webworkers which aren't being considered here) so as long as a loop is looping in javascript, no other code can run so no other code can ever change your condition variable. You will simply have an infinite loop as the loop waits for something that can never change. Eventually the browser will complain that you have unresponsive code running and shut it down.

Because of that, there aren't indeterminate or long wait loops in javascript. It is possible to loop for a second or so just to let some time pass, but that's rarely useful, productive or the best way to write JS code.

Instead, you have to either trigger an event or callback when the condition changes and the interested code can subscribe to that event or register its callback. Or, you have to poll on a timer to see what the condition has changed (the first option is the preferred choice).


If you were designing an API where you wanted to be able to allow some calling code to know when a state changes, usually you would implement either a callback or a promise. The callback approach could look like this:

device.registerStateChangeCallback("type of state interested in", fn);

Then, the API will call the passed in callback function whenever the specified state changes to a new value. It's up to the API whether this is a one-time notification or whether it happens every time the state changes until the callback is deregistered.

So, rather than having the caller wait in a busy loop until the state changes, the caller writes async code (this is how javascript works for this kind of stuff) with a callback that will be called sometime later when the state changes. For example, a caller's code might look like this:

device.registerStateChangeCallback("syncState", function(newState) {
     // caller puts code here that wants to do something when 
     // the syncState has changed
});

If the notifications are meant to be one time only, then you could also use promises and the API simply returns a promise that is resolved when the syncState changes:

device.registerStateChange("syncState").then(function(newState) {
     // caller puts code here that wants to do something when 
     // the syncState has changed
});

The disadvantages of promises are that they are purely single use (one notification only) so if you want multiple notifications, then better to use callbacks. The advantages of promises over callbacks is that they provide a lot of features for synchronizing them with other events (such as sequencing, waiting for a series of events to all finish, coordinating multiple async things, etc...) and they provide better async error handling.

查看更多
做自己的国王
3楼-- · 2019-06-24 03:32

I know this is coming late but es6 has provided a better way to do this. The solution lies in using Generators + Promises.

Take a look at this repository where I had to create Twitter threads where the preceding tweet has to be in reply to the previous and using and this is done by fetching the current tweet's ID and using that to post the next tweet.

Look here => https://github.com/Udokah/tweet-threader

查看更多
Explosion°爆炸
4楼-- · 2019-06-24 03:38

This is indeed a very theoretical question. It would be quite weird to have an async API to get a current status. You would most probably have either:

  • a sync method that gives you the current status;
  • or an async method that would call you back when the status changes. In that case, no need to loop, you just wait for the callback.

But if you do have a specific example of such an API, let us know...

查看更多
混吃等死
5楼-- · 2019-06-24 03:38

This function uses setTimeout so the callback returns without recursion. The requirement is that your environment implement a setTimeout mechanism, as this is not part of JavaScript.

var callback = function callback(result){
   if(result == "some condition"){
       //done
   } else {
       setTimeout(device.getStateAsync(callback), 10);
   }
}

device.getStateAsync(callback);
查看更多
来,给爷笑一个
6楼-- · 2019-06-24 03:39

You can't do this. The reason is that Javascript is single-threaded. While the while loop is running, nothing else runs, so whatever asynchronous task is expected to update the device state will never get a chance to run. Asynchronous tasks in Javascript are run by the main event handler loop. This means that asynch operations won't run until you return from your script back to the event handler, or the script performs a call into the event handler (this only happens when the script waits for user input with confirm or prompt).

查看更多
登录 后发表回答