4

答网友问:Await 一个 Promise 对象到底发生了什么

 2 years ago
source link: https://www.51cto.com/article/718772.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

答网友问:Await 一个 Promise 对象到底发生了什么

作者:LanceZhang 2022-09-15 07:54:59
让我们看一遍 Node.js 官网对 event-loop 的描述。它强调了一个重点:JS code 是以单线程方式被执行的。
4475aa4118d5b57816f9989e766745ac34d15a.png

大家好,我是二哥。

前两篇文章发出来后,有一些网友在后台咨询我一些问题,我把它们归总罗列在一起。这篇文章既是答网友问也是对前两篇的补充和复习。

先放下前两篇的链接。

图解 Node.js 的核心 event-loop

​多图剖析公式 async=Promise+Generator+自动执行器​

图片

图 1:async 函数代码示例

问 0:上一篇所提到的 generator 和自动执行器是运行在不同的线程里面吗?

答 0:无论是 generator 还是自动执行器,都是在 event-loop 线程也就是运行 JS code 的主线程里面运行的。再强调一遍:它俩不是在两个线程里面运行的。

让我们再看一遍 Node.js 官网对 event-loop 的描述。它强调了一个重点:JS code 是以单线程方式被执行的。

The event loop is what allows Node.js to perform non-blocking I/O operations — despite the fact that JavaScript is single-threaded — by offloading operations to the system kernel whenever possible.

问 1:await p 这条语句产生了异步请求了吗?

答 1:不,它没有。await 只是在等待 p 状态的改变,无论状态是从 pending 变成 resolved 还是从 pending 变为 rejected 。

问 2:那异步请求是什么时候产生的?

答 2:是在 Promise 的 executor 里面,执行 setTimeout 时产生的。

​下文把 new Promise() 时传递进去的 callback (resolve, reject)=>{ /* your code */} 称为 executor 。其中参数 resolve 和 reject 是由 Promise 自己实现的。需要注意的是这个 executor 是在 new Promise() 的时候,立即执行的。

假如我们在 executor 里面执行的是 fs.read(fd[, options], callback) 这样的语句,那类似地,异步请求是在调用 fs.read() 时产生的。​

问 3:p 状态改变后,为什么通过 resolve(200) 传递的 200 会变成变量 res 的求值结果?

答 3:这就是为什么说我们需要了解 await 背后的实现原理。我们借助图 2 和图 4 来复习一下。

如图 2 所示,async 函数首先转换成了 generator 函数。但 generator 函数自己是不能自动运行的,所以得搭配一个自动执行器,驱动它往前走。自动执行器如同慈爱的妈妈,而 generator 就像那个懵懂的幼儿。小孩子每走一段路都会停下来,回头看看在他身后寸步不离的妈妈,得到妈妈的鼓励或者奖励后,再走向下一个目标。

图片

图 2:async 函数转换成 generator 函数示例

在讲解图 4 之前,还是有必要再次复习两个重要的概念:yield 表达式和 yield 语句。如图 3 所示:

  • a+b 是表达式,它的求值结果影响到的是 { value: xxx, done: xxx } 中的 value 属性,而 { value: xxx, done: xxx } 是调用者通过迭代器调用 next() 方法的返回值 。
  • yield a+b 是 yield 语句,调用者可以通过给 next() 方法传实参来影响 yield 语句的返回值。比如 next(200) 则会使得变量 a1 为 200 。

图 3 还画出了一个重要的地方:generator 函数执行的暂停点:在 yield 表达式求值结束之后,但 yield 语句返回之前。

图片

图 3:yield 表达式和 yield 语句对比

为了更好更清晰地回答问题 3,二哥给大家画了图 4 。

​这一步开始通过执行器调用 generator。

② 虽然对 generator 真正的调用发生在这里,但 generator 函数在 ② 这步其实什么都没有做,只是立即返回了一个迭代器。

③ 自动执行器从这里开始进入驱动 generator 模式。③ 这一步没有给形参 data​ 赋值,因为我们不能在第一次执行  g.next() 的时候给它注入一个值。

④ 这一步每调用一次  g.next() 就会使得 generator 从上次暂停于 yield 的位置开始运行,直到再次遇到 yield 。

⑤ 所以第一次对  g.next() 调用使得左侧 generator 函数从函数起始位置一直运行直到遇到 yield 。

我们看到 ⑤ 所标识出来的代码执行过程其实是创建了一个 Promise 对象,且在 Promise 的 executor 里面设置了一个 1s 钟的定时器。注意,这个 executor 是在创建 Promise 对象时立即执行的,不过 ⑦ 处的代码要等到 1s 之后才会执行。

⑥ generator 函数暂停之前,先会将 yield 表达式的求值结果通过 { value: xxx, done: xxx} 返回给  g.next() 调用方,也即右图 ④ 位置。 

所以你一定猜到了,右图 ④ 位置的变量 result 为 { value: p, done: false} 。这里的 p 就是 ⑤ 执行过程中产生的 Promise 对象。

通过这样的方式,Promise 对象在 generator 函数和自动执行器之间流转。真是一个巧妙的过程。

那么你在右侧 ⑧ 处看到 result.value.then(callback) 这样的语句就不会感到纳闷了,这是 Promise 的标准用法。当 p 的状态变成 resolved 后,⑧ 处的 callback 自然就会得到运行的机会了。

⑦ 1s 很快,滴答一下过去后,resolve(200) 得以运行。它的运行使得 p 的状态变成 resolved,所以在  ⑧ 处耐心等待的 callback 开始了它的工作。

⑧ 是的,这个时候 data 的值为 200 。这是再自然不过的事,如果你对 Promise 的使用了然于胸的话。

⑨ 自动执行器又一次执行 next(data)​ 。不过这一次给它传了一个实参 200 。所以这一次 ④ 处执行的代码变为: g.next(200) 。

⑩ 自动执行器执行  g.next(200) 必然会驱使 generator 函数动身继续往前赶路。

还记得 generator 函数上次停在哪里休息的吗?对,左侧 ⑤ 处箭头所指的位置。generator 函数恢复运行后干的第一件事就是对 yield 语句求值。

如果像   g.next()​  这样驱动它的话,yield 语句返回的是 undefined 。不过这次我们不一样,因为我们执行的是  g.next(200) 。很巧妙,传给 next() 的实参  200 作为 yield 语句的返回值赋值给了左侧变量 res​ 。

图片

图 4:generator + 自动执行器细节图

让我们再回头看下图 1 的示例代码,我们来做个总结:

  1. await p 语句是个糖衣,它包裹的是 yield p 语句 + 自动执行器。
  2. 所谓 await p 暂停并不是说主线程执行  JS  code 暂停了。相反主线程还在继续执行其它的 JS code 。
  3. await 是在等待 p 的状态发生变化。这个等待时间有多长?这完全取决于创建 p 的时候,  executor 里面何时会调用 resolve() 或 reject() 。
  4. 执行 await p 语句的时候,无论 p 的状态是否已经发生了变化,执行到 await p 都会导致 V8 engine 转而去自动执行器里面执行。这是 yield p 语句使然。
  5. 自动执行器如同一个如影随形的妈妈,她拿到 p 之后,会耐心地等待,直到得到 p 状态改变后的 value 。最后再通过 g.next(value) 把 value 返回给它挚爱的 generator 函数。
图片

图 5:同图 1

责任编辑:姜华 来源: 二哥聊云原生

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK