异步编程03:async-await
async的本质
async是一个关键字,用来声明一个异步函数(AsyncFunction)
,并且一定会返回一个Promise对象
。
async function asyncSum(n1, n2) {
return n1 + n2
}
上面代码等价于
function asyncSum(n1, n2) {
return Promise.resolve(n1 + n1)
}
其实,async可以看成是一种语法糖
,内部引擎执行过程如下:
let asyncFunc = async function () {}
let AsyncFunction = Object.getPrototypeOf(asyncFunc).constructor
let asyncSum = new AsyncFunction('n1', 'n2', 'return n1 + n2')
函数体的执行过程
既然asyncSum是一个异步函数,那么其执行过程是怎样的呢?
async函数的函数体可以看作是由0个或者多个await表达式
组成的。从第一行代码直到第一个await表达式(如果有的话)都是同步运行的。
这样的话,一个不含await表达式的async函数是会同步
运行的。然而,如果函数体内有一个await表达式,async函数就一定会异步执行。
返回值
async 函数一定会返回一个Promise对象,其返回值的规则如下:
- 如果async 函数执行成功,返回的Promise对象的状态为fulfilled,值为return中的值。
- 如果执行失败,那么返回的Promise对象的状态为rejected,值为相关的错误信息。
为什么需要async
引入async func
,其实借鉴了迭代器(iterator)
中的处理方式。
想象一下,对多个前后依赖的异步任务,如果可以像迭代数组一样进行迭代处理,那么异步任务的代码,会更加的清晰易懂。
Promise.then链在处理多个异步任务(各个异步任务是依赖关系,就是说,后面一个任务依赖前一个任务的执行结果)时,需要在每个then中设置需要返回的Promise对象,稍显麻烦。
比如,常见的先读取再写入的例子,用Promise.then链式调用:
readFileTask
.then(readData => {
console.log(`read finished: ${readData}`)
return writeFile('example_copy.json', readData) // 继续写入
})
.then(msg => {
console.log(`write finished: ${msg}`)
})
.catch(err => console.log(err))
如果用async-await:
async function task() {
const r1 = await readFile('example.json')
const r2 = await writeFile('example_copy.json', r1)
}
task()
虽然是异步代码,但是看上去却是同步的方式。也就是说,async-await
能够让你以同步方式
写异步代码。
不是取代Promise
可能有些人认为,有了async-await,Promise就没有存在的必要了。这是错误的看法!
async-await,只是在处理多个异步任务(且各个异步任务是依赖关系,就是说,后面一个任务依赖前一个任务的执行结果)时,会让代码更加清晰,它不是为了取代Promise,也无法取代Promise,二者有不同的使命。Promise的出现是为了解决回调的问题,而async-await,从yield
具有的代码暂停执行
的功能中找到了灵感,提供了类似的异步代码暂停执行的能力,用同步的方式去写异步代码。
当然,async结合await才具有真正的威力,就像generator
一样,其存在的价值是为了服务iterator
。
await
await就容易理解了,光看字面意思大概也能猜出来,它其实就是等待一个异步任务的执行结果
。它只能在async function中使用。
语法:
[返回值] = await 表达式
表达式:一个Promise对象或者任何要等待的值。
返回值:Promise对象的处理结果。如果等待的不是Promise对象,则返回该值本身。
await表达式会暂停当前async function的执行,等待Promise处理完成。
来看一个例子:
async function task() {
console.log('task start..')
const result = await Promise.resolve(100)
console.log(result)
console.log('task finished..')
}
console.log(1)
const r = task()
console.log(2)
上面代码中,会先打印1,然后执行task函数体,接着打印出“task start..”,这里遇到await表达式,会等待异步任务执行,所以,会打印出2,打印出2后,所有同步任务已经执行完毕,继续执行异步代码,当await执行结束后,打印出结果100,最后打印出“task finished..”,所以,结果为:
1
task start..
2
100
task finished..
返回值
我们需要重点关注返回值,一般有如下几种情况:
- 若Promise正常处理(fulfilled),其回调的resolve函数的参数作为await表达式的值,继续执行async function。
- 若Promise发生异常(rejected),await表达式会把Promise的异常原因抛出。
- 如果await后面表达式的值不是一个Promise,则返回该值本身。
最后,再重复一下:
- 当执行到await表达式时,代码会暂停当前async function的执行,等待Promise处理完成。
- await最终拿到的是
结果
,即Promise.resolve中的值(如果成功执行),而不是一个Promise对象。
小结
到这里为止,已经将异步编程说得差不多了。不过,还有一个比较重要的点没有提到,那就是异常处理
,异常处理非常重要,尤其是在Node.js中,因为如果异常处理不当,很可能整个服务器就崩溃了。关于如何正确在异步代码中进行异常处理,请见本系列的最后一篇《异步编程04:异常处理》。