首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为Javascript异步函数的“等待”建立正确的心智模型:生成器的“产量”还是“promise.then()”?

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

Stack Overflow用户
提问于 2019-07-04 16:43:32
回答 3查看 1.4K关注 0票数 6

哪个生成器的产量相对于promise.then()是一个更正确的心理模型来理解‘等待’?

属性比较,通过使用调试器逐步遍历下面的代码段来推断:

等待:

  1. 等待不暂停/挂起正在运行的异步函数的执行。(运行异步函数“运行到完成”,当解释器到达第一个等待时返回一个挂起的承诺。它将立即从调用堆栈中删除。)
  2. 等待着承诺的达成。
  3. await expression将函数的其余代码包装在一个微任务中。

发电机-产量:

  1. 生成暂停正在运行的函数的执行。生成器函数不会“运行到完成”。
  2. yield promise确实确保promise在执行剩余代码之前已经解决了问题。
  3. 产量不会包装或创建微任务。

Promise.then(回调):

  1. 不暂停正在运行的函数的执行。
  2. 在执行回调之前等待承诺解决。
  3. 创建一个微任务(回调)

代码语言:javascript
复制
//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

EN

回答 3

Stack Overflow用户

发布于 2019-07-04 18:24:03

不是一个或另一个。实际上,这两者是同时存在的:async/await = yield + then +一个跑步者。

async function确实被await关键字挂起,就像生成器function*yield关键字挂起一样。在控制流语句中间如何停止和继续执行的机制完全相同。

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

票数 5
EN

Stack Overflow用户

发布于 2019-07-04 20:12:38

承诺和让步不是最容易掌握的,尤其是当你不知道它们是如何工作的时候。那么,让我们从基础开始。首先要理解的是Javascript是单线程的,这意味着它只能同时做一件事情。您仍然能够在“一次”中实现多个事情的方式是因为javascript有一个叫做事件循环的东西。

事件循环基本上如下所示:

代码语言:javascript
复制
while(queue.waitForTasks()) {
   queue.performNextTask();
}

事件循环所做的是检查是否有新的“任务”来运行Javascript。如果有任务的话。然后执行它,直到没有更多的任务需要执行。它将等待它的新任务。这些任务存储在一个称为队列的东西中。

承诺,异步/等待

现在我们了解Javascript如何处理不同的任务。它是如何处理承诺和异步/等待的?promise只不过是一个任务,或者在Javascript的情况下,它包含一个任务,它将被添加到队列中,并在所有任务执行之前执行一次。.then()是一种为您的承诺提供回调的方法,一旦您的解决方案回调被调用,它就会被执行。

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

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

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

代码语言:javascript
复制
const A = async () => {
    console.log(A: Start);
    for (var i = 0; i < 3; i++) {
        await (async () => console.log('A: ' + i));
    }
    console.log('A: Done');
}
代码语言:javascript
复制
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');
}

当您正在调用您的函数时,请像这样等待:

代码语言:javascript
复制
console.log('Executing A');
await A();
console.log('Executing B');
await B();

这将导致:

代码语言:javascript
复制
Executing A
A: Start
A: 0
A: 1
A: 2
A: Done
Executing B
B: Start
B: 0
B: 1
B: 2
B: Done

和跑步:

代码语言:javascript
复制
console.log('Executing A');
A();
console.log('Executing B');
B();

将导致:

代码语言:javascript
复制
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
EN

Stack Overflow用户

发布于 2019-07-04 16:58:51

我不知道正确的心理模型的答案,虽然我真的很想知道。

但我觉得这很有趣

凯尔·辛普森是“你不知道JS”一书的作者,他谈到了“等待工作”是如何在r/Javascript上编辑- 来源

“这是完全不正确的。生成器不会运行到完成,异步的大多数引擎实现实际上都把它们当作生成器来对待。当遇到产量时,生成器就会在本地暂停……字面意思是。等待使用相同的方法。” “不,这都是不正确的胡说八道。大多数引擎对待异步--就像一个生成器一样等待,这肯定会在产生结果时暂停。将promise.then()封装在后续代码中,这将是实现等待的最天真和最低效的方法之一。即使引擎这样做(大多数引擎不这么做),也不意味着这是正确的心智模型。模型的局部暂停就像屈服一样,是正确的心智模型。”

但是当我亲自查看ECMA脚本规范并使用vscode nodejs调试器遍历代码时,“等待”似乎更类似于.then()

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/56891838

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档