Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct mental model for a Javascript async function's 'await': generator's 'yield' vs. 'promise.then()'?

Which of a generator's yield vs. promise.then() is a more* correct mental model for understanding 'await'?

Property comparison, inferred by stepping through the snippet below with a debugger:

await:

  1. await does not pause/suspend the running async function’s execution. (The running async function ‘runs to completion’, returning a pending promise when the interpreter hits the 1st await. It’s then immediately removed from the call stack.)

  2. await waits for the promise to settle.

  3. await expression wraps the rest of a function's code in a microtask.

generator-yield:

  1. yield pauses the running function’s execution. generator functions do not ‘run to completion’.
  2. yield promise does ensure promise has settled prior to executing remaining code.
  3. yield does not wrap or create a microtask.

promise.then(callback):

  1. does not pause the running function’s execution.
  2. waits for promise to settle before executing callback.
  3. creates a microtask (callback)

//promise returning function
function foo(whoCalled) {
   let p = new Promise(function(resolve, reject) { 
     setTimeout( () => {
       console.log('resolving from setTimeout - called by: ' + whoCalled)
       resolve('resolve value') }, .1)
   })
   return p
}

//async await
async function asyncFunc() {
  await foo('async function')
  //rest of running function’s code…
  console.log('async function howdy')
}

//generator yield:
function* gen() {
   yield foo('generator function')
   //rest of running function’s code…
   console.log('generator function howdy')
}

//promise.then():
function thenFunc() {
   let r = foo('promise.then function').then(() => {
       //rest of running function’s code…
       console.log('promise.then() howdy')
   })
   return r
}

//main
function main() {

  //async await
  var a = asyncFunc() 
  console.log(a) //logs Promise { <pending> }
                 //the rest of the code following await foo() runs as a microtask runs once foo() resolves. The call stack was cleared.

  //generator
   var g = gen()
   console.log(g) // logs Object [Generator] {}
   var p = g.next().value
   console.log(p) //logs Promise { <pending> }
   g.next()       //the rest of the code following yield running gen function's code runs. call stack was not cleared.

   //promise.then()
   var x = thenFunc()
   console.log(x) //logs Promise { <pending> }
                   //the then(callback) microtask runs once foo() resolves. The call stack was cleared
}
main()
console.log('main is off the call stack - launch/startup macrotask completing. Event loop entering timer phase.')

And, going beyond this comparison, what is the accurate mental model of what await does under the hood?

await in latest ECMAScript spec for reference: https://www.ecma-international.org/ecma-262/10.0/index.html#await

await in V8 source code: https://github.com/v8/v8/blob/4b9b23521e6fd42373ebbcb20ebe03bf445494f9/src/builtins/builtins-async-function-gen.cc#L252

like image 457
AnonEq Avatar asked Jul 04 '19 16:07

AnonEq


People also ask

Does async await use yield?

A generator function is executed yield by yield i.e one yield-expression at a time by its iterator (the next method) whereas async-await, they are executed sequential await by await. Async/await makes it easier to implement a particular use case of Generators.

What is difference between promises and async await in JavaScript?

Promise is an object representing intermediate state of operation which is guaranteed to complete its execution at some point in future. Async/Await is a syntactic sugar for promises, a wrapper making the code execute more synchronously.

Is Yield same as await?

functionality: yield and await can both be used to write asynchronous code that “waits”, which means code that looks as if it was synchronous, even though it really is asynchronous. await: This is an operator which used to wait for a Promise.

What are asynchronous methods in JavaScript?

Asynchronous programming is a technique that enables your program to start a potentially long-running task and still be able to be responsive to other events while that task runs, rather than having to wait until that task has finished. Once that task has finished, your program is presented with the result.


2 Answers

It's not one or the other. Actually it's both of them together: async/await = yield + then + a runner.

An async function does get suspended by the await keyword just like a generator function* does get suspended by the yield keyword. The mechanism of how the execution gets stopped and resumed in the middle of control flow statements is exactly the same.

What differs is how these continuations are driven, and what the functions return. A generator function creates a generator object when called, and you have to explicitly invoke the next() method from outside to run the code yield by yield. An async function on the other hand creates a promise, and manages the execution by itself. It doesn't wait for external next() calls but runs each asynchronous step as soon as possible. Instead of returning the yielded values from those next() calls, it does Promise.resolve() the awaited values to a promise, and calls its then method passing the continuation as the callbacks. Instead of signalling an "end of iteration" to the caller when reaching a return, it resolves the originally returned promise with the return value.

like image 69
Bergi Avatar answered Sep 30 '22 11:09

Bergi


Promises and yield are not the easiest to grasp, especially not when you don't know how they work under the hood. So let's start with the basics. The first thing to understand is that Javascript is single threaded, which means that it can only do one thing at the same time. The way you are still able to multiple things at 'once' is because javascript has a thing called an event loop.

The event loop is basically looks something like this:

while(queue.waitForTasks()) {
   queue.performNextTask();
}

What the event loop does is check if there are new 'tasks' for Javascript to run. If there is a task. then it gets executed until there are no more tasks left to execute. And it will wait for its new task. These tasks are stored in something that is called a queue.

Promises, Async/Await

Now we understand how Javascript processes the different tasks. How does it work with promises, and async/await? A promise is nothing more than a task, or in the case of Javascript something that holds a task, that will be added to the queue and executed once all tasks before it have been executed. The .then() is a way of providing a callback to your promise that gets executed once your resolve callback is called.

the await [something] keyword tells Javascript, hey put the next [something] on the end of your queue, and get back to me once that [something] has a result to give.

A function that has the async keyword is basically telling Javascript: 'This function is a promise, but execute it immediately'.

The flow of a async function is easiest to grasp/demonstrate with two different async functions A and B like this:

const A = async () => {
    console.log(A: Start);
    for (var i = 0; i < 3; i++) {
        await (async () => console.log('A: ' + i));
    }
    console.log('A: Done');
}
const B = async () {
    console.log(B: Start);
    for (var i = 0; i < 3; i++) {
        await (async () => console.log('B: ' + i));
        await (async () => {/* A task without output */});
    }
    console.log('B: Done');
}

When you are calling your functions with await like this:

console.log('Executing A');
await A();
console.log('Executing B');
await B();

it would result in:

Executing A
A: Start
A: 0
A: 1
A: 2
A: Done
Executing B
B: Start
B: 0
B: 1
B: 2
B: Done

and running:

console.log('Executing A');
A();
console.log('Executing B');
B();

would result in:

Executing A
A: Start       Note: still gets ran before Executing B
Executing B
B: Start
A: 0
B: 0
A: 1
A: 2           Note: A: 2 first because another task in B was put in the queue
A: Done
B: 1
B: 2
B: Done

Understanding this might help to better understand the flow of your application.

yield

The yield keyword is similar to await in the sense that an 'outside force' controls when it continues the flow of the function. In this case not the completion of the promise task, but the generator.next() function

like image 32
Software Person Avatar answered Sep 30 '22 10:09

Software Person