1

My class has a method that does something like this:

async foo(..., callback){
    do{
        for(...){
            await bar1();
            await bar2();
        }
    }
    while(...);
    callback();
}

I want to be able to interrupt the loop on command, so this was my approach at it:

async foo(..., callback){
    this.stop = false; // Reset the property
    do{
        for(...){
            await bar1();
            await bar2();
        }
    }
    while(!this.stop && ...);
    callback();
}

stopTheLoop(){
    this.stop = true;
}

And it looked fine to me. So I test it like:

myObject.foo(..., () => {
    console.log("Finished!");
});
console.log("Async test");
setTimeout(() => {
    myObject.stopTheLoop();
}, 1000);

The behaviour is that i see printed "Async test", which means that the foo() call is correcrly not blocking code execution on the main thread, so i think it's safe to say that the setTimeout function gets called. But the do-while loop never ends, and i never see my "Finished!" callback.

I might not have fully understood async/await in ES6, what am I missing?

YoDevil
  • 41
  • 8
  • Is `bar1` and `bar2` really async? And you know that `async foo(callback){` is somewhat missing the point... – Jonas Wilms Nov 20 '17 at 17:43
  • What is in that `for` loop? Please make [mcve] – Bergi Nov 20 '17 at 17:44
  • bar1 and bar2 are quite too complex to be summarized here, but they're respectivly a feedforward and a backpropagate functions of the Neural Network object. – YoDevil Nov 20 '17 at 17:52
  • @YoDevil You don't need to post your original implementation of them, just an example (e.g. with `setTimeout`) that behaves the same and demonstrates the problem. – Bergi Nov 20 '17 at 17:56
  • 1
    Btw, it's a bad practise to have such a callback in an `async` function. Just omit it and use `myObject.foo(…).then(() => console.log("finished"))` – Bergi Nov 20 '17 at 17:57
  • So, the class (NeuralNetwork) has references to a set of other objects (Layers). The layers are connected one to the other by keeping a reference of the previous and next one. The Layer object has a method that does some pure math(ie. just summing and assigning instructions), and passes its results to the next layer, let's call this 'feed'. So, the bar1 function calls the method 'feed' of the first Layer, which <> (not sure if it can be called recursive if its a different instance) calls the method 'feed' on the next one. When it reaches the last one it chain returns its calcs. – YoDevil Nov 20 '17 at 18:08
  • I have temporarly removed the call to bar2 to make it easier to both investigate and explain – YoDevil Nov 20 '17 at 18:17
  • @YoDevil This doesn't sound asynchronous at all? – Bergi Nov 20 '17 at 18:18
  • "*i think it's safe to say that the setTimeout function gets called.*" - make it sure by putting a log statement in the `setTimeout` callback. Your loop might await resolved promises only, but never go back to the macrotask queue. – Bergi Nov 20 '17 at 18:20
  • Yea that's why i think i didnt quite understand how to use async/await... Looking back at the question, when i saw that "Async test" was getting printed, i thought i had it. How should it be done instead? – YoDevil Nov 20 '17 at 18:21
  • Yea, `setTimeout(() => { console.log("Test"); myObject.stopTheLoop(); }, 1000);` Doesn't print anything. – YoDevil Nov 20 '17 at 18:22

1 Answers1

2

bar1 and bar2 appear not to return promises that actually wait for anything. awaiting a plain value is equivalent to Promise.resolve().then(…), which will by asynchronous enough to continue with console.log("Async test"); but otherwise goes into a loop of running promise tasks, not allowing timeouts and other macro tasks to interrupt it. this.stop never changes because the timeout callback never gets a chance to run. Have a look at What determines the call order of deferred function using promises or setTimeout? and Difference between microtask and macrotask within an event loop context for details.

You can fix this by putting a short actual timeout in your loop to make it asynchronous:

async foo(…) {
    this.stop = false; // Reset the property
    do {
        for(…) {
            bar1();
            bar2();
            await new Promise(resolve => setImmediate(resolve));
        }
    } while (!this.stop && …);
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • You are right that it fixes the issue. It has a huge impact on the performance though, 1ms for each time the loop runs. If i understand correctly i'm faking an async behaviour, is there any other approach i can take to make the loop stop? – YoDevil Nov 20 '17 at 18:46
  • If it's too slow, just don't do it on every iteration but only every tenth or hundreth. – Bergi Nov 20 '17 at 18:50
  • Or try `setImmediate` instead, iirc that should work on the same task queue as well but be faster – Bergi Nov 20 '17 at 18:51
  • Set immediate does seem to have little to no impact, thanks! – YoDevil Nov 20 '17 at 18:58