Javascript异步函数的“等待”的正确心智模型:生成器的“产量”与“ promise.then()”?

时间:2019-07-04 16:43:32

标签: javascript node.js promise async-await event-loop

发电者的收益与诺言之间的哪个。then()是更*正确的心理模型,用于理解'等待'?

属性比较,通过使用调试器逐步浏览以下代码段来推断:

等待:

  1. await不会暂停/暂停正在运行的异步功能的执行。 (正在运行的异步功能“运行至完成”,在解释器达到第一个等待状态时返回未完成的承诺。然后立即将其从调用堆栈中删除。)

  2. await等待诺言完成。

  3. await expression将函数的其余代码包装在微任务中。

发电量:

  1. yield暂停正在运行的功能的执行。生成器功能不会“运行完成”。
  2. yield promise确实确保promise在执行其余代码之前已经解决。
  3. 收益不会包装或创建微任务。

promise.then(回调):

  1. 不暂停正在运行的功能的执行。
  2. 等待承诺完成,然后执行回调。
  3. 创建一个微任务(回调)

//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.')

除了这种比较之外,await在幕后所做的准确的心理模型是什么?

等待最新的ECMAScript规范以供参考: https://www.ecma-international.org/ecma-262/10.0/index.html#await

在V8源代码中等待:https://github.com/v8/v8/blob/4b9b23521e6fd42373ebbcb20ebe03bf445494f9/src/builtins/builtins-async-function-gen.cc#L252

3 个答案:

答案 0 :(得分:3)

这不是一个。实际上,两者都在一起:async / await = yield + then +跑步者。

async function确实被await关键字暂停了,就像生成器function*却被yield关键字暂停了一样。在控制流语句中间停止执行和恢复执行的机制是完全相同的。

不同之处在于这些延续的驱动方式以及函数返回的内容。生成器函数在被调用时会创建一个生成器对象,并且您必须从外部显式调用next()方法以通过yield运行代码yield。另一方面,异步函数会创建一个Promise,并自行管理执行。它不等待外部next()调用,而是尽快运行每个异步步骤。它没有将next()的等待值返回给promise,而是调用其Promise.resolve()方法,将连续性作为回调传递,而不是从那些then调用中返回产生的值。到达return时,它不会向调用者发出“迭代结束”的信号,而是使用返回值来解析最初返回的Promise。

答案 1 :(得分:2)

承诺和收益并不是最容易掌握的,尤其是当您不知道它们如何在幕后工作时。因此,让我们从基础开始。首先要了解的是Javascript是单线程的,这意味着它只能同时执行一项操作。您仍然可以“一次”执行多个操作的方式是因为javascript有一个称为事件循环的事件。

事件循环基本上看起来像这样:

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

事件循环的作用是检查是否有新的“任务”可以运行Javascript。如果有任务。然后它会执行,直到没有其他任务可以执行为止。并且它将等待其新任务。这些任务存储在称为队列的东西中。

承诺,异步/等待

现在,我们了解了Javascript如何处理不同的任务。它如何与promise和async / await一起使用? promise只不过是一个任务,或者在使用Javascript的情况下,它可以容纳一个任务,它将被添加到队列中,并在所有任务执行完毕后立即执行。 .then()是一种为您的诺言提供回调的方法,一旦您的解析回调被调用,该诺言就会被执行。

await [something]关键字告诉Javascript,嘿,将下一个[something]放在您的队列末尾,一旦[something]有结果可给我。

具有async关键字的函数基本上是在告诉Javascript:“此函数是一个承诺,但要立即执行”。

使用两个不同的异步函数A和B这样最容易掌握/演示异步函数的流程:

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');
}

像这样在等待时调用函数:

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

它将导致:

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

并运行:

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

将导致:

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

了解这可能有助于更好地了解您的应用程序流程。

收益

yield关键字与await类似,在某种意义上,“外力”控制着何时继续执行功能。在这种情况下,不是完成诺言任务,而是generator.next()函数

答案 2 :(得分:0)

尽管我真的很想知道,但我不知道正确的心理模型的答案。

但是我发现这很有趣

凯尔·辛普森(Kyle Simpson)的《您不知道JS》一书深入探讨了如何在r / Javascript reddit上进行等待-source

  

”这是完全不正确的。生成器不会运行到完成状态,并且   大多数异步等待引擎实现实际上都将它们视为   发电机。当遇到收益时,生成器在本地   暂停了...从字面上看。等待使用相同的方法。”

     

“不,这都是不正确的废话。大多数引擎将async-await处理   就像发电机一样,它肯定会在产量上局部停顿。   将promise.then()包裹在后续代码中将是实现await的最幼稚且效率最低的方法之一。   引擎做到了(大多数没有做到)并不意味着那是正确的   心理。模型。像屈服一样的局部暂停是正确的心理模型。”

但是当我亲自查看ECMA脚本规范并使用vscode nodejs调试器浏览代码时,await似乎更类似于.then()