async
功能
async 用于声明异步函数。这个关键字可以用在函数声明、函数表达式、箭头函数和方法上:
async function foo() {}
let bar = async function() {};
let baz = async () => {};
class Qux {
async qux() {}
}
返回值
async函数返回的是一个Promise对象,可以使用then方法添加回调函数。
1.使用return关键字返回的值,会被Promise.resolve()包装成一个Promise对象。
async function foo() {
console.log(1);
return 3;
}
// 给返回的期约添加一个解决处理程序
foo().then(value => console.log(value));
console.log(2);
// 1 2 3
2.返回一个期约对象
async function foo() {
console.log(1);
return Promise.resolve(3);
}
// 给返回的期约添加一个解决处理程序
foo().then(value => console.log(value));
console.log(2);
// 1 2 3
3.在async函数中抛出错误会返回拒绝的期约(导致Promise对象变为reject状态)
async function foo() {
console.log(1);
throw 3;
}
// 给返回的期约添加一个拒绝处理程序
foo().catch(reason => console.log(reason));
console.log(2);
// 1 2 3
4.注意 拒绝期约的错误不会被异步函数捕获:
async function foo() {
console.log(1);
Promise.reject(3);
}
// 给返回的期约添加一个拒绝处理程序
foo().catch(reason => console.log(reason));
console.log(2);
// 1 2
// Uncaught (in promise): 3
await
一般情况下,await命令后面是一个Promise对象;如果不是,会被转成一个立即resolve的Promise对象(会被当作已经解决的期约)。
异步函数主要针对不会马上完成的任务,所以需要一种暂停和恢复执行的能力。使用 await关键字可以暂停异步函数代码的执行,等待期约解决。
let p = new Promise((resolve, reject) => setTimeout(resolve, 1000, 3));
p.then((x) => console.log(x)); // 3
使用 async/await 可以写成这样:
async function foo() {
let p = new Promise((resolve, reject) => setTimeout(resolve, 1000, 3));
console.log(await p);
}
foo(); // 等待一秒输出3
注意,await 关键字会暂停执行异步函数后面的代码,让出 JavaScript 运行时的执行线程。
await 关键字同样是尝试解包对象的值,然后将这个值传给表达式,再异步恢复异步函数的执行。
await等待的值
等待一个原始值
async function foo() {
console.log(await 'foo');
}
foo();
// foo
等待一个期约
async function qux() {
console.log(await Promise.resolve('qux'));
}
qux();
// qux
等待会抛出错误的同步操作,会返回拒绝的期约
async function foo() {
console.log(1);
await (() => { throw 3; })();
}
// 给返回的期约添加一个拒绝处理程序
foo().catch(console.log);
console.log(2);
// 1
// 2
// 3
如前面的例子所示,单独的 Promise.reject()不会被异步函数捕获,而会抛出未捕获错误。
对拒绝的期约使用 await 则会释放(unwrap)错误值(将拒绝期约返回)
async function foo() {
console.log(1);
await Promise.reject(3);
console.log(4); // 这行代码不会执行
}
// 给返回的期约添加一个拒绝处理程序
foo().catch(console.log);
console.log(2);
// 1
// 2
// 3
理解执行过程
函数运行时在碰到 await 关键字时,会记录在哪里暂停执行。等到 await 右边的值可用了,JavaScript 运行时会向消息队列中推送一个任务,这个任务会恢复异步函数的执行。
(当函数执行时,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。)
因此,即使 await 后面跟着一个立即可用的值,函数的其余部分也会被异步求值。 如下代码:
async function foo() {
console.log(2);
await null;
console.log(4);
}
console.log(1);
foo();
console.log(3);
//1 2 3 4
(1) 打印 1;
(2) 调用异步函数 foo();
(3)(在 foo()中)打印 2;
(4)(在 foo()中)await 关键字暂停执行,为立即可用的值 null 向消息队列中添加一个任务;
(5) foo()退出;
(6) 打印 3;
(7) 同步线程的代码执行完毕;
(8) JavaScript 运行时从消息队列中取出任务,恢复异步函数执行;
(9)(在 foo()中)恢复执行,await 取得 null 值(这里并没有使用);
(10)(在 foo()中)打印 4;
(11) foo()返回。
async function foo() {
console.log(2);
console.log(await Promise.resolve(8));
console.log(9);
}
async function bar() {
console.log(4);
console.log(await 6);
console.log(7);
}
console.log(1);
foo();
console.log(3);
bar();
console.log(5);
// 1 2 3 4 5 8 9 6 7
(1) 打印 1;
(2) 调用异步函数 foo();
(3)(在 foo()中)打印 2;
(4)(在 foo()中)await 关键字暂停执行,向消息队列中添加一个期约在落定之后执行的任务;
(5) 期约立即落定,把给 await 提供值的任务添加到消息队列;
(6) foo()退出;
(7) 打印 3;
(8) 调用异步函数 bar();
(9)(在 bar()中)打印 4;
(10)(在 bar()中)await 关键字暂停执行,为立即可用的值 6 向消息队列中添加一个任务;
(11) bar()退出;
(12) 打印 5;
(13) 顶级线程执行完毕;
(14) JavaScript 运行时从消息队列中取出解决 await 期约的处理程序,并将解决的值 8 提供给它;
(15) JavaScript 运行时向消息队列中添加一个恢复执行 foo()函数的任务;
(16) JavaScript 运行时从消息队列中取出恢复执行 bar()的任务及值 6;
(17)(在 bar()中)恢复执行,await 取得值 6;
(18)(在 bar()中)打印 6;
(19)(在 bar()中)打印 7;
(20) bar()返回;
(21) 异步任务完成,JavaScript 从消息队列中取出恢复执行 foo()的任务及值 8;
(22)(在 foo()中)打印 8;
(23)(在 foo()中)打印 9;
(24) foo()返回。
async function foo() {
console.log(await Promise.resolve('foo'));
}
async function bar() {
console.log(await 'bar');
}
async function baz() {
console.log('baz');
}
foo();
bar();
baz();
//baz foo bar
await命令
- 正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。
async function f() {
// 等同于
// return 123;
return await 123;
}
f().then(v => console.log(v))
// 123
- 另一种情况是,await命令后面是一个thenable对象(即定义了then方法的对象),那么await会将其等同于 Promise 对象。
- await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到。
async function f() {
await Promise.reject('出错了');
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了
注意,上面代码中,await语句前面没有return,但是reject方法的参数依然传入了catch方法的回调函数。这里如果在await前面加上return,效果是一样的。
- 任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。
async function f() {
await Promise.reject('出错了');
await Promise.resolve('hello world'); // 不会执行
}
有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。
- 第一种方法:将第一个await放在try…catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。
async 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方法,处理前面可能出现的错误。
async function f() {
await Promise.reject('出错了')
.catch(e => console.log(e));
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
// 出错了
// hello world