38.异步函数
原文: http://exploringjs.com/impatient-js/ch_async-functions.html
粗略地说, 异步函数 为使用 Promises 的代码提供了更好的语法。
38.1。异步功能:基础知识
考虑以下异步函数:
以前相当同步的代码等同于以下基于 Promise 的代码:
关于异步函数fetchJsonAsync()
的一些观察:
异步函数标有关键字
async
。在异步函数体内,您可以编写基于 Promise 的代码,就好像它是同步的一样。只要值为 Promise,您只需要应用
await
运算符。该运算符暂停异步函数并在 Promise 结算后恢复它:- 如果履行了 Promise,
await
将返回履行值。 - 如果 Promise 被拒绝,
await
会抛出拒绝值。
- 如果履行了 Promise,
异步函数的结果始终是 Promise:
- 返回的任何值(显式或隐式)用于实现 Promise。
- 抛出的任何异常都用于拒绝 Promise。
fetchJsonAsync()
和fetchJsonViaPromises()
都以完全相同的方式调用,如下所示:
38.1.1。异步构造
JavaScript 具有以下异步版本的同步可调用实体。他们的角色总是真实的功能或方法。
38.1.2。异步函数总是返回 Promises
每个异步函数总是返回一个 Promise。
在异步函数中,您通过return
(A 行)实现结果 Promise:
像往常一样,如果您没有明确地返回任何内容,则会为您返回undefined
:
您通过throw
(A 行)拒绝结果 Promise:
38.1.3。返回的 Promise 没有包装
如果从异步函数返回 Promise p
,则p
成为函数的结果(或者更确切地说,结果“锁定”在p
上并且行为与它完全相同)。也就是说,Promise 并没有包含在另一个 Promise 中。
回想一下,在以下情况下,任何 Promise q
都会被类似地处理:
new Promise((resolve, reject) => { ··· })
内的resolve(q)
.then(result => { ··· })
内的return q
.catch(err => { ··· })
内的return q
38.1.4。 await
:与 Promises 合作
await
运算符只能在异步函数中使用。它的操作数通常是一个 Promise,并导致执行以下步骤:
- 当前的异步功能暂停(类似于
yield
时同步发生器暂停的方式)。 - 继续处理任务队列。
- 一旦 Promise 得到解决,异步函数就会恢复:
- 如果履行了 Promise,
await
将返回履行值。 - 如果 Promise 被拒绝,
await
会抛出拒绝值。
- 如果履行了 Promise,
以下两节提供了更多详细信息。
38.1.5。 await
并兑现了 Promise
如果其操作数最终成为履行的 Promise,await
将返回其履行值:
也允许非 Promise 值,并简单地传递(同步,不暂停异步函数):
38.1.6。 await
并拒绝 Promise
如果其操作数是被拒绝的 Promise,则await
会抛出拒绝值:
Error
的实例(包括其子类的实例)将被特别处理并抛出:
练习:通过异步函数获取 API
exercises/async-functions/fetch_json2_test.js
38.2。术语
让我们澄清几个术语:
异步函数 , 异步方法 :使用关键字
async
定义。异步函数也称为 async / await ,基于作为其语法基础的两个关键字。直接使用 Promises :意味着代码在没有
await
的情况下处理 Promises。基于 Promise 的 :通过 Promises 提供结果和错误的函数或方法。也就是说,异步函数和返回 Promises 的函数都是合格的。
异步 :异步传递结果和错误的函数或方法。这里,任何使用异步模式(回调,事件,Promises 等)的操作都是合格的。唉,事情有点令人困惑,因为“异步功能”中的“异步”是“异步”的缩写。
38.3。 await
很浅(你不能在回调中使用它)
如果你在异步函数中并希望通过await
暂停它,则必须在该函数中执行此操作,不能在嵌套函数中使用它,例如回调。也就是说,暂停是 浅 。
例如,以下代码无法执行:
原因是普通箭头功能不允许await
进入其体内。
好的,让我们尝试异步箭头功能,然后:
唉,这也行不通:现在.map()
(因此downloadContent()
)返回一个带 Promise 的数组,而不是带有(未包装)值的数组。
一种可能的解决方案是使用Promise.all()
解包所有 Promises:
这段代码可以改进吗?是的,它可以,因为在 A 行,我们通过await
展开 Promise,只是通过return
立即重新包装它。我们可以省略await
,然后甚至不需要异步箭头功能:
出于同样的原因,我们也可以省略 B 行中的await
。
练习:异步映射和过滤
exercises/async-functions/map_async_test.js
38.4。 (高级)
所有剩余部分都是高级的。
38.5。立即调用异步箭头函数
如果在异步函数外需要await
(例如,在模块的顶层),则可以立即调用异步箭头函数:
立即调用异步箭头函数的结果是 Promise:
38.6。并发和await
38.6.1。 await
:顺序运行异步函数
如果使用await
为多个异步函数的调用添加前缀,那么这些函数将按顺序执行:
也就是说,otherAsyncFunc2()
仅在otherAsyncFunc1()
完全结束后才开始。
38.6.2。 await
:并发运行异步函数
如果我们想同时运行多个函数,我们需要求助于工具方法Promise.all()
:
这里,两个异步函数同时启动。一旦两者都解决了,await
给我们一个履行值数组或 - 如果至少有一个 Promise 被拒绝 - 一个异常。
回想一下前一章,重要的是当你开始基于 Promise 的计算 - 而不是你如何处理它的结果。因此,以下代码与前一个代码一样“并发”:
38.7。使用异步功能的提示
38.7.1。异步函数同步启动,异步安装
异步函数执行如下:
- 启动异步功能时会创建结果的 Promise
p
。 - 然后身体被执行。执行可以通过两种方式离开正文:
- 执行可以永久保留,同时解决
p
:return
满足p
。- A
throw
拒绝p
。
- 当等待通过
await
解决另一个 Promiseq
时,执行也可以暂时离开。异步功能暂停,执行离开它。一旦q
结算,它就会恢复。
- 执行可以永久保留,同时解决
- Promise
p
在执行首次离开身体后(永久或暂时)返回。
请注意,结果p
的结算通知是异步发生的,Promise 的情况也是如此。
下面的代码演示了异步函数是同步启动的(行 A),然后当前任务完成(行 C),然后结果 Promise 得到解决 - 异步(行 B)。
38.7.2。如果你“发射并忘记”你不需要await
使用基于 Promise 的函数时不需要await
,如果要暂停并等到返回的 Promise 结算,则只需要它。如果你想要做的只是启动异步操作,那么你不需要它:
在此代码中,我们不等待.write()
,因为我们不关心它何时完成。但是,我们确实要等到.close()
完成。
38.7.3。它对await
有意义并忽略结果
即使您忽略其结果,使用await
偶尔也会有意义。例如:
在这里,我们使用await
加入长时间运行的异步操作。这确保了在操作完成后,记录确实发生了 。