async
async是generater函数的语法糖,就是将 Generator 函数的星号*替换成async,将yield替换成await
async对generater函数的改进
- 内置执行器:不需要调用next(),就会自动执行
- 更好的语义:async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果
- 更广的适用性:async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)
- async函数的返回值是Promise:generater函数的返回值是遍历器对象
基本用法
async将你的函数返回值(自动地)转换为promise对象,不需要显式地返回promise对象
async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句1
2
3
4
5
6
7
8
9
10
11
12
13function sleep() {
return new Promise(resolve => {
setTimeout(() => {
console.log('finish')
resolve("sleep");
}, 2000);
});
}
async function test() {
let value = await sleep();
console.log("object");
}
test()
上面代码会先打印 finish 然后再打印 object 。因为 await 会等待 sleep 函数 resolve ,所以即使后面是同步代码,也不会先去执行同步代码再来执行异步代码。
async函数内部return语句返回的值,会成为then方法回调函数的参数
1 | async function f() { |
async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到
1 | async function f() { |
Promise 对象的状态变化
async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
1 | async function getTitle(url) { |
函数getTitle内部有三个操作:抓取网页、取出文本、匹配页面标题。只有这三个操作全部完成,才会执行then方法里面的console.log。
await 命令
正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。
1 | async function f() { |
另一种情况是,await命令后面是一个thenable对象(即定义then方法的对象),那么await会将其等同于 Promise 对象。
await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到
1 | async function f() { |
注意,上面代码中,await语句前面没有return,但是reject方法的参数依然传入了catch方法的回调函数。这里如果在await前面加上return,效果是一样的。
任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。
1 | async function f() { |
错误处理
有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await放在try…catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行1
2
3
4
5
6
7
8
9
10
11async function f() {
try {
await Promise.reject('出错了');
} catch(e) {
}
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
// hello world
另一种方法是await后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误。1
2
3
4
5
6
7
8
9
10async function f() {
await Promise.reject('出错了')
.catch(e => console.log(e));
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
// 出错了
// hello world
独立的异步操作
独立的异步操作(即互不依赖),如果不存在继发关系,最好让它们同时触发。1
2let foo = await getFoo();
let bar = await getBar();
getFoo和getBar是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo完成以后,才会执行getBar,完全可以让它们同时触发。
1 | // 写法一 |
await命令只能用在async函数之中,如果用在普通函数,就会报错。
for await…of
for await…of循环,则是用于遍历异步的 Iterator 接口
1 | async function f() { |
createAsyncIterable()返回一个拥有异步遍历器接口的对象,for…of循环自动调用这个对象的异步遍历器的next方法,会得到一个 Promise 对象。await用来处理这个 Promise 对象,一旦resolve,就把得到的值(x)传入for…of的循环体。
如果next方法返回的 Promise 对象被reject,for await…of就会报错,要用try…catch捕捉。
1 | async function () { |
for await…of循环也可以用于同步遍历器1
2
3
4
5
6
7(async function () {
for await (const x of ['a', 'b']) {
console.log(x);
}
})();
// a
// b
异步 Generator 函数
异步 Generator 函数的作用,是返回一个异步遍历器对象。
在语法上,异步 Generator 函数就是async函数与 Generator 函数的结合。1
2
3
4
5
6async function* gen() {
yield 'hello';
}
const genObj = gen();
genObj.next().then(x => console.log(x));
// { value: 'hello', done: false }
gen是一个异步 Generator 函数,执行后返回一个异步 Iterator 对象。对该对象调用next方法,返回一个 Promise 对象。
async与Event Loop
async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句
- await等待的是右侧的[表达式结果],如果右侧是一个函数,等待的是右侧函数的返回值,如果右侧是表达式则是右侧表达式的值
- await在等待时会让出线程阻塞后面的执行。await的执行顺序为从右到左,会阻塞下面的代码执行,但并不是直接阻塞await的表达式。
- await右侧如果不是promise,await会阻塞下面的代码,会先执行async外面的同步代码,等外面的同步代码执行完成在执行async中的代码。
- 如果await右侧返回的是promise,await也会暂停async下面的代码,先执行async外面的同步代码,等着 Promise 对象 fulfilled,然后把 resolve 的参数作为 await 表达式的运算结果。
1 | var a = 0 |
- 首先函数 b 先执行,在执行到 await 10 之前变量 a 还是 0
- 因为在 await 内部实现了 generators,generators 会保留堆栈中东西,所以这时候 a = 0 被保存了下来
- 因为 await 是异步操作,遇到await就会立即返回一个pending状态的Promise对象
- 暂时返回执行代码的控制权,使得函数外的代码得以继续执行,所以会先执行 console.log(‘1’, a)
- 这时候同步代码执行完毕,开始执行异步代码,将保存下来的值拿出来使用,这时候 a = 10
- 然后后面就是常规执行代码了
1 | async function async1() { |
- js主线程走到并输出“start”
- 由于setTimeout执行的优先级低于主线程上的任务和Promise上的任务所以要等到最后
- 遇到async1(),输出“async1”,再执行async2()函数输出“async2”
- 由于走到async2()时遇到await,所以要跳出async1()函数体外
- 接着代码往下跑,执行new Promise对象,输出“promise1”
- Promise回调进入到微任务队列中,继续往下走,输出“script end”
- 现在回到async1()函数体内中的await async2()处,由于await后面接着async2()返回的是Promise对象还要再次跳出
async1()函数体外继续执行以外的代码 - 这时候正好有Promise队列中的then需要执行,于是输出“promise2”
- setTimeout仍然靠后优先级低,再次回到async1()函数体内,接着执行输出“async1 end”
- 最后剩下主线程和Promise队列的任务都执行完成了,就输出“setTimeout”