异步是JavaScript的一大难点之一,因此js的异步解决方案也是多种多样的。从最早的回调函数,到Promise对象,到Generator函数,再到现在的async/await。很多人都认为async/await是异步操作的终极解决方案,今天就来简单介绍下它。

async/await 这名字取得就很语义化,async声明一个异步function,await用于等待异步function执行完成。并且语法规定,await只能出现在async函数中。

aysnc

async到底做了什么?

我们看一下下面的代码

1
2
3
4
5
async function test() {
return 'hello world'
}
console.log(test())

我们来看看打印出了什么

1
Promise { 'hello world' }

没错,它返回了一个Promise对象。从文档中我们知道await用于等待一个Promise对象。
但是在上面的例子中直接返回了一个字符串,它不是Promise对象,async函数会通过Promise.resolve()把它封装成一个Promise对象。

我们回顾一下Promise的特点——无等待。如果这个async函数没有await的话,它会立即执行返回一个Promise对象,不会阻塞后面代码的运行。

await

我们知道await是在等待一个async函数完成,而async函数会返回一个Promise对象,因此await表达式的运算结果就是这个Promise对象resolve()的值。

继续看个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function test1() {
return 'test'
}
function testAsync(n) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(n)
}, 1000)
})
}
async function test() {
const result1 = await test1()
console.log(result1)
const result2 = await testAsync(999)
console.log(result2)
}
test()
console.log('同步代码')

上述代码首先打出了”同步代码”,随后又立即打出”test”,并在一秒后打出了”999”。
我们可以得出以下结论:

  • await会阻塞它所在的异步函数的后面代码的执行(因为它需要等到testAsync函数的结果才会往下执行),但是不会阻塞异步函数之外的代码的执行(因为先打印的”同步代码”),其实它内部的阻塞都被封装到一个Promise对象中异步执行,我想可能是因为这样await才必须在async函数中吧。
  • 如果await等到的不是Promise对象的话会将它转成立即resolve的Promise对象,并将该值作为await表达式的结果(打印完”同步代码”之后立即打出”test”);如果是的话就等着Promise对象resolve(),然后将resolve的结果作为await表达式的结果(一秒后打印”999”)。

在async函数中,这样的代码看起来就像是同步代码,await等待到结果才执行后面的语句,但是却不会阻塞async函数之外的代码的执行。

async/await

刚开始可能会感觉async/await就是将Promise中的then变成了用await的方式书写,好像并没有什么优势。

我们先举个栗子:
假设一个功能需要多个步骤完成,并且每个步骤都是异步的,而且后面的步骤依赖于前面步骤的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function asycnTest(time) {
return new Promise(resolve => {
setTimeout(() => {
resolve(time + 500)
}, time)
})
}
function step1(time) {
console.log(`step1用时${time}`)
return asycnTest(time)
}
function step2(time) {
console.log(`step2用时${time}`)
return asycnTest(time)
}
function step3(time) {
console.log(`step3用时${time}`)
return asycnTest(time)
}

先看看用Promise要怎么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function promiseDoIt() {
console.time('promiseDoIt')
step1(1000)
.then(time2 => step2(time2))
.then(time3 => step3(time3))
.then(result => {
console.log(`结果是${result}`)
console.timeEnd('promiseDoIt')
})
}
promiseDoIt()
// step1用时1000
// step2用时1500
// step3用时2000
// 结果是2500
// promiseDoIt: 4507.135ms

上述三个步骤一共用时1000+1500+2000共4500ms,和console.time()/console.timeEnd()运算结果一致。
如果用async/await来写会怎么样呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
async function asyncDoIt() {
console.time('asyncDoIt')
const time2 = await step1(1000)
const time3 = await step2(time2)
const result = await step3(time3)
console.log(`结果是${result}`)
console.timeEnd('asyncDoIt')
}
asyncDoIt()
// step1用时1000
// step2用时1500
// step3用时2000
// 结果是2500
// asyncDoIt: 4504.294ms

运行结果都是一样的,但是这看起来就像同步代码,非常清晰。
再改一下需求,如果后面步骤依赖前面每一个步骤的结果,又该怎么改呢?反正用async/await修改是很方便的,只需要将前面步骤的结果当参数传递进去就行了,至于Promise的写法,好像就有点麻烦了…

另外,Promise对象不会一直是resolve啊,也会reject的,所以需要将await放在try…catch代码块中对错误进行处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
async function asyncTest() {
try {
await Promise.reject('出错了')
} catch (err) {
console.log(err);
}
}
// 或者直接使用Promise的catch
async function asyncTest() {
await Promise.reject('出错了').catch(err => {
console.log(err)
});
}

这样才不会阻塞代码的正常运行

今天就先到这吧,以后有了新的理解再补充