0

Sorry in advance for the long post! Below is a code example I'm reviewing and the log outputs:

const a = () => new Promise( resolve => { setTimeout( () => resolve('result of a()'), 1000); }); const b = () => new Promise( resolve => { setTimeout( () => resolve('result of b()'), 500); }); const c = () => new Promise( resolve => { setTimeout( () => resolve('result of c()'), 1100); }); // async generator function const MyAsyncGenerator = async function*() { yield await a(); yield await b(); yield await c(); }; // generator object const gen = MyAsyncGenerator(); // get 'gen' values (async () => { console.log(await gen.next()) console.log(await gen.next()) console.log(await gen.next()) console.log(await gen.next()) })(); (async () => { await Promise.all([a(), b(), c()]).then(res => console.log(res)) })() 
{ value: 'result of a()', done: false } [ 'result of a()', 'result of b()', 'result of c()' ] { value: 'result of b()', done: false } { value: 'result of c()', done: false } { value: undefined, done: true } 

I'm trying to figure out why Promise.all([...]).then() is logged second. Here's my best explanation but would really appreciate any additional insights:

  1. Since there are two anonymous async IIFEs (Immediately Invoked Function Expressions), while the first gen.next() evaluates the result of a(), the .then() method on the new Promise returned by Promise.all() is pushed to the microtask queue

  2. After the first await gen.next() is logged to the console, the call stack is empty, so the event loop pushes .then() to the call stack next

I'm ultimately unsure whether it has to do with the task vs. microtask queue or when Promise.all() is fulfilled relative to the generator iterations. My understanding is that setTimeout() goes to the task queue, but if we include Promise.all() in the same code block under the generator iterations, the logs are in order:

(async () => { console.log(await gen.next()) console.log(await gen.next()) console.log(await gen.next()) console.log(await gen.next()) Promise.all([a(), b(), c()]).then(res => console.log(res)) })(); 
{ value: 'result of a()', done: false } { value: 'result of b()', done: false } { value: 'result of c()', done: false } { value: undefined, done: true } [ 'result of a()', 'result of b()', 'result of c()' ] 
3
  • 1
    Promise.all([a(), b(), c()]) will resolve after 1100 ms. The second gen.next() will be called after 1000 ms and resolve after an additional 500 ms - 1500 ms > 1100 ms. Commented Jul 23, 2021 at 5:44
  • 1
    I'm trying to figure out why Promise.all([...]).then() is logged second > async IIFE's here don't wait for anything. The call to Promise.all() does not wait for any await resolution, because its not using any of the awaited results. Commented Jul 23, 2021 at 5:54
  • 1
    await specifically means "leave the current function, and see what else is there to do in the current program". All things happen when their timer hits, nothing in your program chains that Promise.all() to "after the generator is done". Commented Jul 23, 2021 at 6:51

1 Answer 1

1

Here is your program with timestamps of what happens when.

The @ x ms is meant to be absolute time (idealized, of course), T+x ms is meant to be relative time. Things with @ 0 ms happen "at the same time", when the program starts.

const wait = (value, ms) => new Promise(resolve => setTimeout(() => resolve(value), ms)); const a = () => wait('result of a', 1000); const b = () => wait('result of b', 600); const c = () => wait('result of c', 1100); const MyAsyncGenerator = async function*() { yield await a(); // T+1000 ms yield await b(); // T+ 600 ms yield await c(); // T+1100 ms }; const gen = MyAsyncGenerator(); // @ 0 ms (async () => { console.log(await gen.next()); // @ 1000 ms console.log(await gen.next()); // @ 1600 ms console.log(await gen.next()); // @ 2700 ms console.log(await gen.next()); // @ 2700 ms })(); // @ 0 ms (async () => { await Promise.all([a(), b(), c()]) // @ 0 ms .then(res => console.log(res)); // @ 1100 ms })(); // @ 0 ms 

From this it's obvious that the Promise.all() will be done between the generator's first and second output.

Note that your second function is an example of useless use of async/await. Nothing follows the await, so waiting makes no sense. It would be better written as

(() => { Promise.all([a(), b(), c()]) .then(res => console.log(res)); })(); 

and since the IIFE does not make a whole lot of sense here, either, it would be even better as

Promise.all([a(), b(), c()]) .then(res => console.log(res)); 

Bonus

const gen2 = MyAsyncGenerator(); (async () => { console.log(await gen2.next()); console.log(await gen2.next()); console.log(await gen2.next()); console.log(await gen2.next()); })().then(() => { Promise.all([a(), b(), c()]) .then(res => console.log(res)); }); 
Sign up to request clarification or add additional context in comments.

4 Comments

Thanks a lot for the detailed explanation! I didn't appreciate the fact that await only blocks the thread within its scope, but when thinking about it, it definitely makes sense, hence the async concept. So I would either have to chain Promise.all() the way you did or expect them to run simultaneously.
@S.D. also provided helpful context that await doesn't impact Promise.all() because it doesn't depend on the awaited result to execute. Thanks again!
@buddyco await does not block at all, and there is only one thread. Maybe think of it like this: await puts a bookmark in the current function, so to speak, and gives control back to the event loop. Wenn the anticipated event (promise resolution) occurs, control returns back to the function at the last bookmark position.
Super helpful way to think about it, thanks a lot!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.