I think therefore I exist

ES2017 中的 Async function

    Javascript

callback

在没有 Promise 之前的时代,在 JS 中写 callback 简直是一种痛苦的煎熬

比如我们要下载一个文件,下载完成之后需要在成功的回调函数里去读写文件

抽象的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function processData(url) {
downloadFile({
//... networking
success: function(data) {
readFile(data, function(err, file) {
if(!err) {
file.write(function(data) {
//... write file
})
}
})
}
})
}

这样的代码我们称作 "callback hell",回调函数可以是无尽的深渊,那么维护起来会相当困难,简直不要太考验工程师的眼力

Promise

那么 ES2015 引入了 Promise 之后,回调函数的写法有了挺大的改善

我们可以利用 Promise 对上面的代码稍加改造:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function processData(url) {
return new Promise((resolve, reject) => {
downloadFile({
success: resolve
})
})
}

processData.then(data => {
return new Promsie((resolve, reject) => {
readFile(data, (err, file) => {
if (err) {
reject(err)
} else {
resolve(file)
}
})
})
}).then(file => {
file.write(function(data) {
//... write file
})
}).catch(err => {
console.error(err.message)
})

这样我们使得代码的可读性变得更强了,也更好维护了,但是依然避免不了要在 Promise 中写回调函数的问题

async/await

这时候 ES2017 又推出了更好的解决方案,那就是 Async Function

利用 async function 可以定义一个异步的函数,返回一个 AsyncFunction Object

1
2
async function fn(){}
Object.prototype.toString.call(fn) // "[object AsyncFunction]"

async function 被调用的时候,它会返回一个 Promise,这个 Promise 会根据函数体内的返回值来 resolve,或者是根据函数体内抛出的异常来 reject

1
2
let result = fn()
console.log(r) // Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: undefined}

当函数体为空时,return 值自然是 undefined 所以最终 fn 返回给我们的是一个以 undefined 来 resolve 的 Promise

如果函数体内有 await 表达式的时候,那么 async function 会停止执行并等待 await 的表达式的执行结果以确定最终 Promise 用来 resolve 的值

MDN 上的注解是这么说的:

The purpose of async/await functions is to simplify the behavior of using promises synchronously and to perform some behavior on a group of Promises. Just as Promises are similar to structured callbacks, async/await is similar to combining generators and promises.

重点在于 async/await 简化了我们同步地使用 Promise 来产生基于一组 Promise 的行为,这句话有点难理解,我们来看看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function resolveAfter2s(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x)
}, 2000)
})
}

async function add(x) {
const a = await resolveAfter2s(20)
const b = await resolveAfter2s(30)
return x + a + b
}

add(10).then(v => {
console.log(v) // prints 60 after 4 seconds.
})

这样就很容易看懂了,我们的 add 函数看起来是同步的,但是其实是利用了 promise 而实现了异步

总结

那么有了 async/await 之后,我们可以更好地分隔我们的代码,使得代码耦合度更低,逻辑更清晰