node.js - Correct mental model for a Javascript async function's 'await': generator's 'y

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

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

Property parison, 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 pletion’, 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 pletion’.
  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 pleting. Event loop entering timer phase.')

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

Property parison, 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 pletion’, 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 pletion’.
  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 pleting. Event loop entering timer phase.')

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

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

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

Share Improve this question edited Jul 5, 2019 at 15:49 AnonEq asked Jul 4, 2019 at 16:43 AnonEqAnonEq 2852 silver badges9 bronze badges 5
  • 1 await is like .then() – zer00ne Commented Jul 4, 2019 at 16:51
  • "await does not pause/suspend the execution." and "when the interpreter hits the 1st await. It’s then immediately removed from the call stack." do seem contradictory to me. What do you mean by that paragraph? – Bergi Commented Jul 4, 2019 at 18:13
  • babeljs.io/repl can be helpful in figuring this out. Copy paste your code there and see how it simulates the await keyword. – Todd Chaffee Commented Jul 4, 2019 at 19:18
  • @Bergi 1st quote = describes a pause in a function’s code for later resumption (not running to pletion), vs. 2nd quote = describes wrapping the remainder of a function's code in a microtask, hitting the end curly brace and returning (run to pletion). – AnonEq Commented Jul 5, 2019 at 14:12
  • @ToddChaffee Good tip. Is it correct to say the following? Transpiling can produce functionally equivalent vanilla Javascript. And while this may be helpful for inferring what's going on 'under the hood', it could be misleading? (for example: transpiled async await may not map to the ECMAScript async await spec) – AnonEq Commented Jul 5, 2019 at 15:18
Add a ment  | 

3 Answers 3

Reset to default 6

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.

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 pletion of the promise task, but the generator.next() function

I don't know the answer to the correct mental model here, though I would really like to know.

But I found this interesting

Kyle Simpson author of 'You Don't Know JS' chimed in on how await works on r/Javascript reddit - source:

"This is entirely incorrect. Generators do not run-to-pletion, and most engine implementations of async-await actually treat them like generators. When a yield is encountered, the generator is locally paused... literally. Await uses the same approach."

"No, this is all incorrect nonsense. Most engines treat async-await like a generator, which definitely does localy pause at a yield. Wrapping promise.then() around subsequent code would be one of the most naive and inefficient ways to implement await. Even if an engine did that (most don't) that doesn't mean that's the proper mental. model. Local pausing like yield is the proper mental model."

But when I personally look at the ECMA Script spec myself and walk though code with the vscode nodejs debugger, await seems much more analogous to .then()

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1742301769a4418161.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信