异步函数async/await
async/await 是 ES8 新增的语法规范,用来以同步代码的方式执行异步。
async
async 用于声明异步函数。这个关键字可以用在函数声明、函数表达式、箭头函数和方法上。
async function foo() {}
let bar = async function() {};
let baz = async () => {};
class Qux {
async qux() {}
这使得函数具有异步特征,并且代码仍是同步求值。在参数或闭包方面,异步函数具有 JS 函数的正常行为。
//foo()函数仍然会在后面的指令之前被求值
async function foo() {
console.log(1);
foo();
console.log(2);
如果异步函数使用 return 关键字有返回值(没有 return 会返回 undefined),这个值会被 Promise.resolve() 包装成一个 Promise 对象。异步函数始终返回 Promise 对象。在函数外部调用这个函数可以得到它返回的 Promise。
async function foo() {
console.log(1);
return 3;
// 给返回的 Promise 添加一个解决处理程序
foo().then(console.log);
console.log(2);
直接返回一个 Promise 对象也是一样的:
async function foo() {
console.log(1);
return Promise.resolve(3);
// 给返回的 Promise 添加一个解决处理程序
foo().then(console.log);
console.log(2);
await
await 可以暂停异步函数代码的执行,等待 Promise 解决。
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 关键字会暂停执行异步函数后面的代码,让出 JS 运行时的执行线程。这个行为与生成器函数中的 yield 关键字相同。
await 和 JS 的一元操作相同。可以单独使用,也可以在表达式中使用。
// 异步打印"foo"
async function foo() {
console.log(await Promise.resolve('foo'));
foo();
// foo
// 异步打印"bar"
async function bar() {
return await Promise.resolve('bar');
bar().then(console.log);
// bar
// 1000 毫秒后异步打印"baz"
async function baz() {
await new Promise((resolve, reject) => setTimeout(resolve, 1000));
console.log('baz');
baz();
// baz(1000 毫秒后)
await 必须在异步函数中使用,不能在顶级上下文如 <script> 标签或模块中使用。不过可以定义并立即调用异步函数:
//下面两段代码实际是相同的
async function foo() {
console.log(await Promise.resolve(3));
foo();
// 立即调用的异步函数表达式
(async function() {
console.log(await Promise.resolve(3));
})();
异步函数的特质不会扩展到嵌套函数。await 关键字只能出现在异步函数的定义中。在同步函数内部使用 await 会抛出 SyntaxError:
// 不允许:await 出现在了箭头函数中
function foo() {
const syncFn = () => {
return await Promise.resolve('foo');
console.log(syncFn());
// 不允许:await 出现在了同步函数声明中
function bar() {
function syncFn() {
return await Promise.resolve('bar');
console.log(syncFn());
// 不允许:await 出现在了同步函数表达式中
function baz() {
const syncFn = function() {
return await Promise.resolve('baz');
console.log(syncFn());
// 不允许:IIFE 使用同步函数表达式或箭头函数
function qux() {
(function () { console.log(await Promise.resolve('qux')); })();
(() => console.log(await Promise.resolve('qux')))();
停止和恢复执行
//下面的例子中按顺序调用了3个函数,但它们的输出结果顺序是相反的:
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
// bar
// foo
async/await 中真正起作用的是 await。而 async 关键字仅作为标识符。异步函数如果不包含 await 关键字,执行基本与普通函数没有区别:
async function foo() {
console.log(2);
console.log(1);
foo();
console.log(3);
JS 在运行中遇到 await 时,会记录暂停执行的位置。直到 await 右边的值可以使用,JS 运行时会向消息队列中推送一个任务,这个任务会恢复异步函数的执行。
即使 await 后面有立即可用的值,函数的其余部分也会被异步求值:
async function foo() {
console.log(2);
await null;
console.log(4);
console.log(1);
foo();
console.log(3);
如果 await 后面是一个 Promise,为了执行异步函数,实际上会有两个任务被添加到消息队列并且被异步求值:
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);