javascript基础学习系列三百八十三:期约与异步函数

等待会抛出错误的同步操作,会返回拒绝的期约:

 console.log(1); 
 await (() => { throw 3; })(); 
} 
// 给返回的期约添加一个拒绝处理程序
foo().catch(console.log);
console.log(2); 
// 1 
// 2 
// 3 

如前面的例子所示,单独的 Promise.reject()不会被异步函数捕获,而会抛出未捕获错误。不过,对拒绝的期约使用 await 则会释放(unwrap)错误值(将拒绝期约返回):

 console.log(1); 
 await Promise.reject(3); 
 console.log(4); // 这行代码不会执行
} 
// 给返回的期约添加一个拒绝处理程序
foo().catch(console.log); 
console.log(2); 
// 1 
// 2 
// 3

await 的限制
await 关键字必须在异步函数中使用,不能在顶级上下文如

 console.log(await Promise.resolve(3)); 
} 
foo(); 
// 3 
// 立即调用的异步函数表达式
(async function() { 
 console.log(await Promise.resolve(3)); 
})(); 
// 3 

此外,异步函数的特质不会扩展到嵌套函数。因此,await 关键字也只能直接出现在异步函数的定义中。在同步函数内部使用 await 会抛出 SyntaxError。
下面展示了一些会出错的例子:

function foo() { 
 const syncFn = () => { 
 return await Promise.resolve('foo'); 
 }; 
 console.log(sy
 } 
// 不允许: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')))(); 
}

停止和恢复执行
使用 await 关键字之后的区别其实比看上去的还要微妙一些。比如,下面的例子中按顺序调用了 3个函数,但它们的输出结果顺序是相反的:

 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 关键字,其执行基本上跟普通函数没有什么区别:

 console.log(2); 
} 
console.log(1); 
foo(); 
console.log(3);
// 1 
// 2 
// 3

要完全理解 await 关键字,必须知道它并非只是等待一个值可用那么简单。JavaScript 运行时在碰到 await 关键字时,会记录在哪里暂停执行。等到 await 右边的值可用了,JavaScript 运行时会向消息队列中推送一个任务,这个任务会恢复异步函数的执行。
因此,即使 await 后面跟着一个立即可用的值,函数的其余部分也会被异步求值。下面的例子演示了这一点:

 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()返回。
如果 await 后面是一个期约,则问题会稍微复杂一些。此时,为了执行异步函数,实际上会有两个
任务被添加到消息队列并被异步求值。下面的例子虽然看起来很反直觉,但它演示了真正的执行顺序:①

 console.log(2); 
 console.log(await Promise.resolve(8)); 
 console.log(9); 
} 
async function bar() {

① TC39 对 await 后面是期约的情况如何处理做过一次修改。修改后,本例中的 Promise.resolve(8)只会生成一个异步任务。因此在新版浏览器中,这个示例的输出结果为 123458967。实际开发中,对于并行的异步操作我们通常更关注结果,而不依赖执行顺序。
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
// 6
// 7
// 8
// 9

运行时会像这样执行上面的例子:
(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()返回。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值