随着node 7.6.0正式实装async/await函数,js的异步编程变的比以往更加容易。但是,在我们全面投入async/await的怀抱之前,有必要对这个特性做一些细致的了解。
书写形式
基本上,任何一个函数都可以成为async函数,以下都是合法的书写形式:
- 函数声明
async function foo () {}
- 函数表达式
const foo = async function () {}
- 方法定义
const obj = { async foo () {} }
- 箭头函数
async () => {}
async函数总是返回Promise
即使返回值只是一个primitive值,async函数也会通过return自动将返回值包装成一个Promise对象返回。
因此,下面两组函数是等价的。
正常 (Fulfill)
// async函数
async function foo () {
return 'a'
}
// Promise
function foo () {
return Promise.resolve('a')
}
复制代码
异常 (Reject)
// async函数
async function foo () {
throw new Error('error')
}
// Promise
function foo () {
return Promise.reject(new Error('error'))
}
复制代码
注意:当返回值本身就是一个Promise对象时,async函数的return并不会对返回值进行二次包装。
await总是按顺序执行
使用async函数之前,我们还得搞清楚它的运行机制。尤其是在执行顺序上,完全用同步的思维也许并不适用于async函数。
考虑下面的代码
function asyncGet (x) {
return new Promise(resolve => setTimeout(() => {
console.log('a')
resolve(x)
}, 500))
}
async function test () {
console.log('b')
const x = 3 + 5
console.log(x)
const a = await asyncGet(1)
console.log(a)
const b = await asyncGet(2)
console.log(b)
console.log('c')
return a + b
}
const now = Date.now()
console.log('d')
test().then(x => {
console.log(x)
console.log(`elapsed: ${Date.now() - now}`)
})
console.log('f')
复制代码
-
async函数和普通函数一样按顺序执行,同时,在执行到await语句时,返回一个Promise对象
-
await可以理解为将async函数挂起,直到等待的Promise被fulfill或者reject,再继续执行之后的代码
-
async函数的返回值和普通Promise没有区别
因此,上面代码输出应该是
d
b
8
f
a
1
a
2
c
3
elapsed: 1010
复制代码
注意 d 和 f 中间的输出
让我们再来看一个混合了Promise的版本。
function asyncGet (x) {
return new Promise(resolve => setTimeout(() => {
console.log('a')
resolve(x)
}, 500))
}
async function test () {
console.log('b')
const x = 3 + 5
console.log(x)
const [a, b] = await Promise.all([
asyncGet(1),
asyncGet(2)
])
console.log('c')
return a + b
}
const now = Date.now()
console.log('d')
test().then(x => {
console.log(x)
console.log(`elapsed: ${Date.now() - now}`)
})
console.log('f')
复制代码
输出结果
d
b
8
f
a
a
c
3
elapsed: 509
复制代码
注意到elapsed的差别了吗?这就是为什么我们说await总是顺序执行的。不同的await之间无法并行执行,想要真正的完全异步还得借助类似Promise.all这样的方法。
async函数和callback
await只能能影响直接包裹它的async函数。因此在callback函数中的await并不会挂起整个async函数的执行。
一种常见的错误
async function getAll (vals) {
return vals.map(v => await asyncGet(v))
}
复制代码
这段代码有语法错误,await并不在async函数内部。如果给map的callback加上async呢?
async function getAll (vals) {
return vals.map(async v => await asyncGet(v))
}
复制代码
这段代码虽然能执行,但还有两个问题。
-
返回一个Promise对象的数组,并不是我们期待的value数组
-
await只会暂停map的callback,因此map完成时,不能保证asyncGet也全部完成
正确的写法还得借助Promise.all
async function getAll (vals) {
return Promise.all(vals.map(v => asyncGet(v)))
}
复制代码
总结
从上我们可以看出,Promise是async函数的基础,想要愉快的使用async函数,必须对Promise有比较深入的理解。甚至一些常见的任务,仅仅依靠async函数无法实现。
希望大家看完本文后能对async函数有个更全面的认识,这样使用起来才会更加顺手。