异步函数
,也称为
async/await
(语法关键字),是
ES8
规范新增的,是
ES6
中
Promise
在
ECMAScript 函数
中的应用。
为什么需要 async/await ?
ES8 的
async/await
主要是为了
解决利用异步结构组织代码的问题
.
举个最简单的例子,下面的 Promise 超时后会进行 resolve,提供了 4 种写法:
new Promise((resolve)=>{
setTimeout(()=>{
resolve('解决了');
},1000);
}).then((value)=>{
console.log(value);
new Promise((resolve)=>{
setTimeout(resolve, 1000, '解决了');
}).then((value)=>{
console.log(value);
function handler(value) {
console.log(value);
new Promise((resolve)=> setTimeout(resolve, 1000, '解决了')).then(handler);
function handler(value) {
console.log(value);
let p = new Promise((resolve)=> setTimeout(resolve, 1000, '解决了'));
p.then(handler);
上述的 4 种写法,后一种都是前一种的优化改进,但是仔细观察发现,其实每一种写法的改进其实都不大. 因为任何需要访问这个 Promise 所产生值的代码,都需要以处理程序 (then + cllback) 的形式来接收这个值. 而上面的只是一个简单的需求,还没有其他多余的逻辑,仍旧显得比较复杂,可想而知如果你的使用场景很复杂,堆积组合的代码将会变得非常不直观.
相比于上面,下面的写法显然要更容易、更直观的被理解:
async function handler() {
let rs = await new Promise((resolve) => setTimeout(resolve, 1000, '解决了'));
console.log('inner = ',rs);
handler();
async 关键字
async 关键字用于 声明异步函数,可以用在 函数声明、函数表达式、箭头函数 和 方法 上.
async function example() {}
let example = async function() {};
let example = async () => {};
class example {
async exampleHandler() {}
async 关键字可以让函数具有 异步特征,但总体上其代码仍然是同步求值的.
异步函数的 返回值(没有显示的 return 就会返回 undefined) 默认是一个被 Promise.resolve() 包裹的 Promise 对象,当然直接返回一个 Promise 对象也是一样的.
async function example() {
console.log("example");
let rs = example();
console.log('main');
console.log(rs);
rs.then(console.log);
异步函数的 返回值 期待一个实现 thenable 接口的对象,但这并不是严格要求的。如果返回的是 实现 thenable 接口 的对象,则这个对象可以由提供给 then() 的处理程序 “解包”。如果不是,则返回值就被当作状态为 fulfilled 的 Promise.
PS:实现 thenable 接口的对象,简单理解就是一个包含 then 方法的对象。“解包” 这里可以理解为把实现了 thenable 接口的对象中的 then 方法,当作在实例化 Promise 时要传入的 executor,如:new Promise(executor).
async function example() {
return 'example';
example().then(console.log);
async function example() {
return ['example'];
example().then(console.log);
let obj = {
then(resolve, reject) {
console.log("obj.then run...");
resolve('解决了');
async function example() {
return obj;
example().then(console.log);
与在 Promise 中一样,在 异步函数 中 抛出错误 会返回状态为 rejected 的 Promise. 同样的,外部的 try/catch 无法进行捕获.
function example() {
console.log('example run ...');
throw Error('出错了');
try {
example();
} catch (error) {
console.log('error = ', error);
async function example() {
console.log('example run ...');
throw Error('出错了');
try {
example().then(undefined, (reason) => {
console.log('then onReject = ', reason);
} catch (error) {
console.log('error = ', error);
await 关键字
因为 异步函数 主要针对不会马上完成的任务,所以需要一种 暂停 和 恢复 执行的能力。使用 await 关键字可以暂停异步函数代码的执行,等待 Promise 进入 settled 状态。
settled 状态,即 Promise 状态变更为 fulfilled 或 rejected.
await 关键字会暂停执行 异步函数 后面的代码,让出 JavaScript 运行时 的执行线程,这一点与 生成器函数(generator function) 中的 yield 关键字是一致的.
await 关键字会尝试 “解包” 对象的值,然后将这个值传给表达式,再异步恢复异步函数的执行.
“解包” 这里可以理解为把 await 后面的 Promise 在 settled 状态下的 value 或 reason 进行返回.
async function handler() {
let rs = await new Promise((resolve) => setTimeout(resolve, 1000, '解决了'));
console.log(rs);
handler();
await 关键字期望一个实现 thenable 接口的对象,这和 async 的返回值一样不是严格要求。如果是实现 thenable 接口的对象,则这个对象可以由 await 来 “解包”。如果不是,则这个值就被包装成状态为 fulfilled 状态的 Promise.
async function example() {
console.log(await 'example');
example();
async function example() {
console.log(await ['example']);
example();
let obj = {
then(resolve, reject) {
resolve('解决了');
async function example() {
console.log(await obj);
example();
async function example() {
console.log(await Promise.resolve('解决了'));
example();
await 关键字在等待会 抛出错误 的 同步操作,会返回状态为 rejected 的 Promise. 和 async 关键字一样,外部的 try/catch 无法进行捕获.
async function example() {
console.log('start throw error');
await (() => {
throw Error('抛出异常了');
})();
console.log('end throw error');
try {
example().catch(error => {
console.log("promise catch = ",error);
} catch (error) {
console.log("try catch = ", error);
await 关键字必须在 异步函数 中使用,不能在顶级上下文如:<script>
标签或 模块 中使用.
async/await 暂停和恢复执行
async/await 中真正起作用的是 await,可以把 async 关键字简单的当作一个 标识符. 因为如果 异步函数 中不使用 await 关键字,其执行基本上跟普通函数没有什么区别,但是对于 异步函数 的返回值还是会和 普通函数 有区别,这一点在上面有说明.
async function example1() {
console.log(await new Promise((resolve, _) => {
setTimeout(resolve('example1'),0);
async function example2() {
console.log(await 'example2');
async function example3() {
console.log('example3');
example1();
example2();
example3();
简单理解上面的输出顺序:
JavaScript 运行时在碰到 await 关键字时,会记录在哪里暂停执行.
等到 await 右边的值可用了,JavaScript 运行时会向 消息队列 中推送一个任务,这个任务会 恢复异步函数的执行.
并且会按在 消息队列 中的顺序,依次恢复执行.
因此,即使 await 后面跟着一个 立即可用的值,函数的其余部分也会被 异步求值.
下面举个例子,详细介绍一下具体的过程:
async function async1() {
console.log(2);
console.log(await Promise.resolve(8));
console.log(9);
async function async2() {
console.log(4);
console.log(await 6);
console.log(7);
console.log(1);
async1();
console.log(3);
async2();
console.log(5);
一起来分析下执行过程:
(1) 执行 console.log(1),输出 1
(2) 执行 async1() 异步函数:
(2.1) 执行 console.log(2),输出 2
(2.2) 遇到 console.log(await Promise.resolve(8)),此时碰到了 await 关键字先暂停执行,向消息队列中添加一个 Promise 在 settle 之后且值为 8 的任务
(3) 此时 async1() 函数先退出执行
(4) 执行 console.log(3),输出 3
(5) 执行 async2() 异步函数:
(5.1) 执行 console.log(4),输出 4
(5.2) 遇到 console.log(await 6),从上面对于 await 的介绍中可以知道,它等价于 console.log(await Promise.resolve(6)),此时碰到了 await 关键字先暂停执行,向消息队列中添加立即可用值为 6 的任务
(6) 此时 async2() 函数先退出执行
(7) 执行 console.log(5),输出 5
(8) 到这,主线程已经执行完毕
(9) JavaScript 运行时从消息队列中取出解决 await 后面 Promise 的处理程序
(10) JavaScript 运行时从消息队列中取出恢复执行 async1()的任务及值 8
(10.1) 此时 console.log(await Promise.resolve(8)) 等价于 console.log(8),输出 8
(10.2) 执行 console.log(9),输出 9
(10.3) 此时 async1 执行完成并返回
(11) JavaScript 运行时从消息队列中取出恢复执行 async2()的任务及值 6
(11.1) 此时 console.log(await 6) 等价于 console.log(6),输出 6
(11.2) 执行 console.log(7),输出 7
(11.3) 此时 async2 执行完成并返回
其实也可以和 事件循环 结合在一起看,下面给出了简单的图解:
公众号 @「熊的猫」
粉丝