作者:Alan Storm翻译:疯狂的技术宅
原文:https://alanstorm.com/what-is...
未经允许严禁转载
当我第一次看到 async/await 的描述时有点困惑,直到我了解了 async/await 的出处,才领悟到它的价值。本文我们来讨论 javascript 世界中最先进的异步编程思想之一。
什么是异步函数
javascript 中的异步函数是有着一些“超能力”的常规函数。要定义异步函数,需要在其定义前加上 async
关键字。
// File: hello-async.js
// const notAnAsyncFunction = function() {
// console.log("Hello Not Async")
// }
const helloAsync = async function() {
console.log("Hello Async")
}
// 如果用 javascript 的箭头函数语法
// const helloAsync = async () => {
// console.log("Hello Async")
//}
helloAsync()
运行上面的程序,得到下面的输出。
$ node hello-async.js
Hello Async
在获得超能力之前,应该先明白异步函数不是什么。默认情况下异步函数不会异步运行你的代码。看下面的代码。
// File: hello-async.js
const helloAsync = async () => {
/*2.*/ console.log("Hello Async")
}
/*1.*/ console.log("Before Function Call")
helloAsync()
/*3.*/ console.log("After Function Call")
如果运行上面的程序,将会得到以下输出。
$ node hello-async.js
Before Function Call
Hello Async
After Function Call
我们可以看到程序是按顺序输出的,即 helloAsync
中的工作不是异步完成的。
你可能还注意到了代码中带编号的注释( /*1.*/
)本文中的这类注释可以帮你理解代码的执行顺序。
永远返回 Promise
现在我们知道异步函数不是什么,但是它们是什么呢?异步函数的第一个超级功能:总是返回一个 promise。看下面的代码
// File: hello-async.js
const helloWorld = async function() {
return "Hello World"
}
console.log(helloWorld())
我们希望这个小程序输出字符串 Hello World。但是,如果运行它:
$ node hello-async.js
Promise { 'Hello World' }
可以看到它返回一个实例化的 Promise 对象,并且该对象已经确定为我们返回的值(字符串 Hello World)。异步函数获取了我们返回的字符串,并将其转换为一个 Promise。
如果想在程序中实现这个 Promise 的值,就需要使用 then
方法.
// File: hello-async.js
const helloWorld = async function() {
return "Hello World"
}
let promise = helloWorld()
promise.then(function(result){
console.log(result)
})
如果你从异步函数返回显式的 Promise,它将会变得更加复杂。比我们想要的复杂得多。简短的版本是 async 函数返回新的 Promise,这个 Promise 最终将解析为你返回的 Promise 的已解决值。如果运行这段代码
// File: hello-async.js
const helloWorld = async function() {
const promise = new Promise(function(resolve) {
setTimeout(function(){
resolve("Hello Promise")
})
})
return promise
}
const promise = helloWorld()
console.log(promise)
promise.then(function(result) {
console.log("] " + result)
})
你将得到一个 Promise,Promise 的值将变为 Hello Promise。
$ node hello-async.js
Promise { <pending> }
] Hello Promise
有意思的是,如果你用 then
方法返回任何对象的话,
// File: hello-async.js
const helloWorld = async function() {
const promise = {
then: function() {
}
}
return promise
}
const pendingPromise = helloWorld()
console.log(pendingPromise)
异步函数会将其视为 promise。
$ node hello-async.js
Promise { <pending> }
这些对象有时称为“then-able”。因此,你可以将任意第三方 Promise 库与异步函数一起使用(只要该库使用 then
方法即可)。
Await
异步函数总是返回 promise。要知道为什么总是返回 Promise,就需要了解异步函数的第二种超级功能:使用 await
语句实现 promise 的能力。
在异步函数中编写代码时,可以使用 await
语句。该语句在异步函数外部不可用。
$ node
> const promise = new Promise(function(){})
undefined
> await promise
await promise
^^^^^
SyntaxError: await is only valid in async function
await
语句期望在其右侧有一个 promise 对象。当你使用 await
语句时,javascript 会暂停 async
函数的执行,等待 promise 返回一个值,然后继续执行。
看下面的程序:
// File: hello-async.js
const createPromise = function(message) {
const promise = new Promise(function(resolve, reject){
setTimeout(function(){
if(message) {
/*1.*/ resolve(message)
} else {
reject("No Message Provided")
}
}, 0)
})
return promise
}
const promise = createPromise("Hello Promise")
const main = function(promise) {
/*2.*/ console.log("Starting Main")
promise.then(function(result){
/*4.*/ console.log(result)
})
/*3.*/ console.log("Ending Main")
}
main(promise)
这是另一个用来演示证明 promise 如何运作的小程序。该程序创建一个Promise对象,该对象将异步返回 Hello Promise 文本。然后,在名为 main
的函数中,我们要求该 promise 作为其值,然后记录这个值。执行程序得到以下输出。
$ node hello-async.js
Starting Main
Ending Main
Hello Promise
也就是说,在 promise 中的异步工作运行之前,main
函数将完成执行。Promise 本身迫使我们使用回调来获取Promise 的值。我们无法在 main
的作用域内获得 Promise 的值。
下面是相同的程序,但是用了 async
函数和 await
语句。
// File: hello-async.js
const createPromise = function(message) {
const promise = new Promise(function(resolve, reject){
setTimeout(function(){
if(message) {
/*2.*/ resolve(message)
} else {
reject("No Message Provided")
}
}, 0)
})
return promise
}
const promise = createPromise("Hello Promise")
const main = async function(promise) {
/*1.*/ console.log("Starting Main")
const message = await promise
/*3.*/ console.log(message)
/*4.*/ console.log("Ending Main")
}
main(promise)
运行程序得到以下输出:
$ node hello-async.js
Starting Main
Hello Promise
Ending Main
也就是说 main
函数将会输出第一行
// File: hello-async.js
console.log("Starting Main")
然后当我们使用 await
时,该函数将会停止并等待 promise 执行,然后输出其结果
// File: hello-async.js
const message = await promise
console.log(message)
最后输出最后一条日志。
// File: hello-async.js
console.log("Ending Main")
换句话说,即使我们用的是 Promise,此函数中代码也可以使我们以同步的方式执行。
这就是 async/await 的力量。 原始延续传递风格异步API创建了 “callback-heck”,每个异步上下文具有多层嵌套回调。 Promise 对此进行了一些清理,使我们可以将事情限制在单个级别的回调中。 async/await 组合是它的下一个改进——使用 await
,我们可以无需使用回调就能够得到异步工作的结果。
异步的对立面?
async/await 组合是强大的,但是可能有些东西会困扰你。
然后当我们使用 await
时,该函数将停止”并等待 promise 执行,然后输出其结果
如果 await
等待执行完成,那么这个异步代码会怎样?这听起来像异步代码的“对立面”。
根据前面已经告诉你的一切,你的顾虑是正确的。但是我们仍然没有讨论异函数能如何与你程序的其余部分进行交互。
当你的异步函数正在等待 promise 解决时,javascript 将跳回到调用上下文并继续执行主程序。如果我们用以下内容替换 main
函数及其调用,会得到什么结果呢?
// File: hello-async.js
/* ... */
const main = async function(promise) {
console.log("Starting Main")
const message = await promise
console.log(message)
console.log("Ending Main")
}
console.log("Before Main")
main(promise)
console.log("After Main")
如果运行 上面这段程序,则会得到以下输出。
$ node
Before Main
Starting Main
After Main
Hello Promise
Ending Main
也就是说序将会按照以下顺序执行
const main = async function(promise) {
/* 3.*/ console.log("Starting Main")
/* 4.*/ const message = await promise
/* 6.*/ console.log(message)
/* 7.*/ console.log("Ending Main")
}
/* 1.*/ console.log("Before Main")
/* 2.*/ main(promise)
/* 5.*/ console.log("After Main")
虽然代码在 main
中按顺序执行,但是调用 main
则意味着整个程序不会按顺序执行。相反,执行将离开 main
函数,在 main
作用域内完成执行,最后返回完成 main
的执行。
返回两次?
该执行模型存在一些歧义。例如下面这段程序的异步函数
// File: hello-async.js
const createPromise = function(message) {
const promise = new Promise(function(resolve, reject){
setTimeout(function(){
if(message) {
/*1.*/ resolve(message)
} else {
reject("No Message Provided")
}
}, 0)
})
return promise
}
const someFunction = async function() {
const promise = createPromise("Hello Async")
const toReturn = await promise
return "The Promise Returned: " + toReturn
}
result = someFunction()
console.log(result)
如果 await
使函数提前返回,那么 result
中会是什么?不会是 "The Promise Returned: " + toReturn
,因为该代码尚未运行。让我们运行程序并找出答案。
$ node hello-async.js
Promise { <pending> }
这就是为什么异步函数总是返回 promise 的原因。当我们使用 await
时,程序无法知道该函数的实际返回值。而是函数调用返回一个 Promise。那么是否需要异步函数返回的值?你可以使用 promise 的 then
方法,或者如果你在另一个异步函数中,则可以用 await
返回的 promise。
总结
我花了一段时间才了解 async/await 是如何对异步事务状态进行整体改进的。尽管 async/await 确实简化了使用 promise 的各个函数的执行过程,但你仍然必须处理越来的越非线性代码。
我意识到,async/await 在系统程序员和客户端程序员之间提供了清晰明了的界限。客户端程序员可以像执行同步任务一样使用 promise,但是在幕后并没有真正阻止 promise。系统程序员承担管理整个程序的异步复杂性的负担。
如果你考虑一下这几天有多少系统在工作,这是有道理的。在像 express 之类的路由框架中,普通的客户端程序员实际上并没有真正考虑 express 内部的工作,他们只是编写路由函数。在支持 async/await 的路由框架中,客户端程序员可以编写其路由并 await ...
,直到他们满意为止。express 核心团队将需要处理这些路由函数所返回的 promise。
async/await 模式使服务端开发人员有机会从客户端代码中“消除”异步编程的复杂性。虽然还处于初期,但随着更多框架开始考虑使用 await
来构建系统,对于尚未具备编写异步代码的深层背景的程序员,现代 javascript 的可访问性将有所提高。
本文首发微信公众号:前端先锋
欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章
欢迎继续阅读本专栏其它高赞文章:
- 深入理解Shadow DOM v1
- 一步步教你用 WebVR 实现虚拟现实游戏
- 13个帮你提高开发效率的现代CSS框架
- 快速上手BootstrapVue
- JavaScript引擎是如何工作的?从调用栈到Promise你需要知道的一切
- WebSocket实战:在 Node 和 React 之间进行实时通信
- 关于 Git 的 20 个面试题
- 深入解析 Node.js 的 console.log
- Node.js 究竟是什么?
- 30分钟用Node.js构建一个API服务器
- Javascript的对象拷贝
- 程序员30岁前月薪达不到30K,该何去何从
- 14个最好的 JavaScript 数据可视化库
- 8 个给前端的顶级 VS Code 扩展插件
- Node.js 多线程完全指南
- 把HTML转成PDF的4个方案及实现
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。