3

I am using a generator function to emulate an iterable task queue:

function* taskQueue() {
    for(let i=0; i<10; i++) {
        yield new Promise((resolve) => {
            setTimeout(() => resolve(i), (11 - i)*1000)
        })
    }
}

The timeouts should cause the promises to resolve in reverse order (assuming all started at the same time), with the longest completing in 11 seconds.

Iterating over the promises, and using Promise.then(), the code takes the length of the longest timeout (11 seconds) to complete:

function then() {
    let q = taskQueue();
    for(i of q) {
        i.then(console.log)
    }
}

However, if I await the promises instead then they resolve in order and it takes linear time (66 seconds) to complete:

async function awaiting() {
    let q = taskQueue();
    for(i of q) {
        let n = await i;
        console.log(n);
    }
}

It appears that this is because the next promise is not being yielded by taskQueue before the previous one resolves, since if I push them onto an array and then await, the code executes in 11 seconds:

async function awaiting2() {
    let q = taskQueue();
    let tasks = [];
    for(i of q) {
        tasks.push(i);
    }
    for(i of tasks) {
        let n = await i;
        console.log(n);
    }
}

So why is it that the promises are resolved concurrently in the case of then(), but not in the case of awaiting()?

wolfson109
  • 898
  • 6
  • 10
  • _"The timeouts should cause the promises to resolve in reverse order"_ Not sure what you mean by "reverse order"? What is the expected result? – guest271314 Feb 24 '19 at 01:09
  • The timeout is set to 11 - i seconds. So the 1st promise yielded will have a timeout of 11 - 0 = 11 seconds, the 2nd 10 seconds and so on up to the 10th which will have a timeout of 1 second. – wolfson109 Feb 24 '19 at 01:11
  • Still not sure what the expected result is? For the entire procedure at the code at the first example to take a total of 11 seconds to complete? Or for the procedure to take the sum of 11 + 10 + 9 + N-1 through to 0 to complete? What problem are you trying to resolve? – guest271314 Feb 24 '19 at 01:14
  • The point was to create a set of promises that would all take varying amounts of time to complete. If the code is executing in a concurrent manner then I would expect total execution time to be close to the longest timeout, as in the case when I use Promise.then(). What is curious is that when awaiting the promises instead the execution time is much longer. – wolfson109 Feb 24 '19 at 01:20
  • The code at the first example does take 11 seconds to complete. Using `async/await` executes the code at `awaiting` executes in sequential order. Which is also the expected result. The pattern used is a choice, and depends on what you are trying to achieve. – guest271314 Feb 24 '19 at 01:20
  • Note, the first code example can be adjusted to output the same result as the `async/await` example. – guest271314 Feb 24 '19 at 01:29

2 Answers2

3

This is because Promises are naturally asynchronous, as in the rest of your program will continue executing regardless of what state the Promise is in (Pending, Resolved, Error).

By using async keyword you are telling the function block to wait for every Promise specified with the await keyword to wait for that Promise to resolve before continuing execution, which makes that function block synchronous.

By calling .then() method on the Promise you are leaving the Promise execution asynchronous, but .then() will be called whenever it completes, regardless of whatever else is going on in your program.

cantuket
  • 1,582
  • 10
  • 19
  • Interesting! Articles I've read about `async`/`await` implied that the behaviour was the same as calling `.then()`, but from your answer it seems that if I want to handle promises asynchronously then I should use `.then()` rather than `await`. Thanks. – wolfson109 Feb 24 '19 at 01:29
  • 1
    @wolfson109, also you can do something like this: `Array.from(taskQueue()).forEach(async (i) => { let n = await i; console.log(n); })`. This code also been asynchronous despite the fact that uses `async/await`. – Dmitry Chebakov Feb 24 '19 at 01:39
  • Yup, `async/await` is meant to more elegantly handle synchronicity of Promises. What you may be looking for is [Promise.all()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all), which will allow you to `await` (or call `.then()`) on the resolution of a series of Promises. – cantuket Feb 24 '19 at 01:40
2

The code at the first example can be adjusted to output the same result which the code that uses asycn/await does using .next() and .then() in a pattern that uses recursion "a non-terminating procedure that happens to refer to itself." What are the boundaries of recursion?

function* taskQueue() {
  for (let i = 0; i < 10; i++) {
    yield new Promise((resolve) => {
      setTimeout(() => resolve(i), (11 - i) * 1000)
    })
  }
}

const queue = taskQueue();

const awaiting = ({value:promise, done}, q) => 
  done ? {done} : promise.then(data => (console.log(data), awaiting(q.next(), q)));

awaiting(queue.next(), queue)
.then(console.log)
.catch(console.error)
guest271314
  • 1
  • 15
  • 104
  • 177