本文摘自PHP中文网,作者青灯夜游,侵删。
ES8 引入的 async/await
在 JavaScript 的异步编程中是一个极好的改进。它提供了使用同步样式代码异步访问 resoruces
的方式,而不会阻塞主线程。然而,它们也存在一些坑及问题。在本文中,将从不同的角度探讨 async/await
,并演示如何正确有效地使用这对兄弟。
前置知识
async 作用是什么
从 MDN 可以看出:
async
函数返回的是一个 Promise 对象。async 函数(包含函数语句、函数表达式、Lambda表达式)会返回一个 Promise 对象,如果在函数中 return
一个直接量,async 会把这个直接量通过 Promise.resolve()
封装成 Promise 对象。
如果 async 函数没有返回值, 它会返回 Promise.resolve(undefined)
。
await 作用是什么
从 MDN 了解到:
await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,await 可以等任意表达式的结果)。
如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
这就是 await 必须用在 async 函数中的原因。async 函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。
async/await 的优点
async/await
带给我们的最重要的好处是同步编程风格。让我们看一个例子:
1 2 3 4 5 6 7 8 9 |
|
很明显,async/await
版本比 promise
版本更容易理解。如果忽略 await
关键字,代码看起来就像任何其他同步语言,比如 Python。
最佳的地方不仅在于可读性。async/await
到今天为止,所有主流浏览器都完全支持异步功能。
本地浏览器的支持意味着你不必转换代码。更重要的是,它便于调试。当在函数入口点设置断点并跨过 await
行时,将看到调试器在 bookModel.fetchAll()
执行其任务时暂停片刻,然后它将移动到下一个.filter
行,这比 promise 代码要简单得多,在 promise 中,必须在 .filter
行上设置另一个断点。
另一个不太明显的优点是 async
关键字。 async
声明 getBooksByAuthorWithAwait()函数返回值确保是一个 promise,因此调用者可以安全地使用 getBooksByAuthorWithAwait().then(...)
或await getBooksByAuthorWithAwait()
。 想想下面的例子(不好的做法!):
1 2 3 4 5 6 7 |
|
在上述代码中,getBooksByAuthorWithPromise
可能返回 promise(正常情况下)或 null 值(异常情况下),在异常情况下,调用者不能调用 .then()
。有了async
声明,这种情况就不会出现了。
async/await 可能会产生误导
一些文章将 async/wait 与 Promise 进行了比较,并声称它是 JavaScript 下一代异步编程风格,对此作者深表异议。async/await
是一种改进,但它只不过是一种语法糖,不会完全改变我们的编程风格。
从本质上说,async 函数仍然是 promise。在正确使用 async 函数之前,你必须先了解 promise,更糟糕的是,大多数时候你需要在使用 promises 的同时使用 async 函数。
考虑上面示例中的 getBooksByAuthorWithAwait() 和getbooksbyauthorwithpromise() 函数。请注意,它们不仅功能相同,而且具有完全相同的接口!
这意味着 getbooksbyauthorwithwait() 将返回一个 promise,所以也可以使用 .then(...)
方式来调用它。
嗯,这未必是件坏事。只有 await
的名字给人一种感觉,“哦,太好了,可以把异步函数转换成同步函数了”,这实际上是错误的。
async/await
那么在使用 async/await
时可能会犯什么错误呢?下面是一些常见的例子。
太过串行化
尽管 await
可以使代码看起来像是同步的,但实际它们仍然是异步的,必须小心避免太过串行化。
1 2 3 4 5 6 7 8 |
|
上述代码在逻辑上看似正确的,然而,这是错误的。
await bookModel.fetchAll()
会等待fetchAll()
直到fetchAll()
返回结果。- 然后
await authorModel.fetch(authorId)
被调用。
注意,authorModel.fetch(authorId)
并不依赖于 bookModel.fetchAll()
的结果,实际上它们可以并行调用!然而,用了 await,两个调用变成串行的,总的执行时间将比并行版本要长得多得多。
相关阅读 >>
更多相关阅读请进入《javascript》频道 >>
Vue.js 设计与实现 基于Vue.js 3 深入解析Vue.js 设计细节
本书对 Vue.js 3 技术细节的分析非常可靠,对于需要深入理解 Vue.js 3 的用户会有很大的帮助。——尤雨溪,Vue.js作者