ES8 中的 async/await —— 异步函数

简介: ES8 中的 async/await —— 异步函数

image.png


异步函数

异步函数,也称为 async/await (语法关键字),是 ES8 规范新增的,是 ES6PromiseECMAScript 函数 中的应用。

为什么需要 async/await ?

ES8 的 async/await 主要是为了 解决利用异步结构组织代码的问题.

举个最简单的例子,下面的 Promise 超时后会进行 resolve,提供了 4 种写法:

// 写法 1
   new Promise((resolve)=>{
      setTimeout(()=>{
        resolve('解决了');
      },1000);
    }).then((value)=>{
      console.log(value);
    });
   // 写法 2
   new Promise((resolve)=>{
      setTimeout(resolve, 1000, '解决了');
    }).then((value)=>{
      console.log(value);
    });
   // 写法 3
   function handler(value) {
      console.log(value);
   }
   new Promise((resolve)=> setTimeout(resolve, 1000, '解决了')).then(handler);
   // 写法 4
   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"); // 1. example
  }
  let rs = example();
  console.log('main'); // 2. main
  console.log(rs); // 3. Promise {<fulfilled>: undefined}
  rs.then(console.log); // 4. undefined
复制代码
  • 异步函数的 返回值 期待一个实现 thenable 接口的对象,但这并不是严格要求的。如果返回的是 实现 thenable 接口 的对象,则这个对象可以由提供给 then() 的处理程序 “解包”。如果不是,则返回值就被当作状态为 fulfilledPromise.

PS:实现 thenable 接口的对象,简单理解就是一个包含 then 方法的对象。“解包” 这里可以理解为把实现了 thenable 接口的对象中的 then 方法,当作在实例化 Promise 时要传入的 executor,如:new Promise(executor).

// 1. 返回原始值
 async function example() {
    return 'example';
 }
 example().then(console.log); // example
// 2. 返回复杂类型(且没有实现 thenable 接口)
async function example() {
   return ['example'];
}
example().then(console.log); // ['example']
// 3. 返回一个实现了 thenable 接口的非 Promise 对象
  let obj = {
    then(resolve, reject) {
      console.log("obj.then run...");
      resolve('解决了');
    }
  }
  async function example() {
    return obj;
  }
  example().then(console.log);
  // 输出顺序:obj.then run...   解决了
复制代码
  • 与在 Promise 中一样,在 异步函数抛出错误 会返回状态为 rejectedPromise. 同样的,外部的 try/catch 无法进行捕获.
// 1. 普通函数 throw Error
  function example() {
    console.log('example run ...'); // 1. example run ...
    throw Error('出错了');
  }
  try {
    example();
  } catch (error) {
    console.log('error = ', error);
    // 2. error =  Error: 出错了
   //          at example (index.html:41)
   //          at index.html:45
  }
  // 2. 异步函数 throw Error
  async function example() {
    console.log('example run ...'); // 1. example run ...
    throw Error('出错了');
  }
  try {
    // 用 Promise 上的 then 方法
    example().then(undefined, (reason) => {
      console.log('then onReject = ', reason);
      // 2. then onReject =  Error: 出错了
     //       at example (index.html:41)
    //        at index.html:45
    });
    // 用 Promise 上的 catch 方法
    // example().catch(console.log);
  } catch (error) {
   // 和 Promise 一样,内部抛出的异常会被 reject 处理,并不会被 try/catch 捕获
    console.log('error = ', error); // 不会被执行
  }
复制代码

await 关键字

因为 异步函数 主要针对不会马上完成的任务,所以需要一种 暂停 和 恢复 执行的能力。使用 await 关键字可以暂停异步函数代码的执行,等待 Promise 进入 settled 状态。

settled 状态,即 Promise 状态变更为 fulfilledrejected.

  • await 关键字会暂停执行 异步函数 后面的代码,让出 JavaScript 运行时 的执行线程,这一点与 生成器函数(generator function) 中的 yield 关键字是一致的.
  • await 关键字会尝试 “解包” 对象的值,然后将这个值传给表达式,再异步恢复异步函数的执行.

“解包” 这里可以理解为把 await 后面的 Promisesettled 状态下的 valuereason 进行返回.

async function handler() {
      let rs = await new Promise((resolve) => setTimeout(resolve, 1000, '解决了'));
      console.log(rs); // 大约 1s 后输出:'解决了'
  }
  handler();
复制代码
  • await 关键字期望一个实现 thenable 接口的对象,这和 async 的返回值一样不是严格要求。如果是实现 thenable 接口的对象,则这个对象可以由 await“解包”。如果不是,则这个值就被包装成状态为 fulfilled 状态的 Promise.
// 1. await + 原始值
    async function example() {
      console.log(await 'example'); // example
    }
    example();
    // 2. await + 返回复杂类型(且没有实现 thenable 接口)
    async function example() {
      console.log(await ['example']); // ['example']
    }
    example();
    // 3. await + 实现了 thenable 接口的非 Promise 对象
    let obj = {
      then(resolve, reject) {
        resolve('解决了');
      }
    }
    async function example() {
      console.log(await obj); // 解决了
    }
    example();
   // 4. await + Promise 对象
    async function example() {
      console.log(await Promise.resolve('解决了')); // 解决了
    }
    example();
复制代码
  • await 关键字在等待会 抛出错误同步操作,会返回状态为 rejectedPromise. 和 async 关键字一样,外部的 try/catch 无法进行捕获.
// 定义函数
    async function example() {
      console.log('start throw error'); // 1. start throw error
      await (() => {
        throw Error('抛出异常了');
      })();
      console.log('end throw error'); // 不会输出,因为 await 后抛出了异常,直接向外返回了 rejected 状态的 Promise 
    }
    // 执行函数
    try {
      example().catch(error => {
         console.log("promise catch = ",error); // 2. 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();
    // 输出顺序:example3 example1 example2
复制代码

简单理解上面的输出顺序:

  • 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
    2
    3
    4
    5
    8
    9
    6
    7
复制代码

一起来分析下执行过程:

(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 执行完成并返回 其实也可以和 事件循环 结合在一起看,下面给出了简单的图解:

image.png


目录
相关文章
|
7月前
|
前端开发
async和await 优雅处理异步
async和await 优雅处理异步
|
7月前
|
前端开发
Await和Async是什么?跟Promise有什么区别 使用它有什么好处
Await和Async是什么?跟Promise有什么区别 使用它有什么好处
|
1月前
|
前端开发
如何使用async/await解决Promise的缺点?
总的来说,`async/await` 是对 Promise 的一种很好的补充和扩展,它为我们提供了更高效、更易读、更易维护的异步编程方式。通过合理地运用 `async/await`,我们可以更好地解决 Promise 的一些缺点,提升异步代码的质量和开发效率。
35 5
|
1月前
|
前端开发 JavaScript
async/await和Promise在性能上有什么区别?
性能优化是一个综合性的工作,除了考虑异步模式的选择外,还需要关注代码的优化、资源的合理利用等方面。
38 4
|
7月前
|
前端开发 JavaScript 开发者
阿珊带你深入理解 async/await 函数
阿珊带你深入理解 async/await 函数
|
4月前
|
C#
C# async await 异步执行方法
C# async await 异步执行方法
56 0
|
6月前
|
JSON 前端开发 JavaScript
ES6引入Promise和async/await解决异步问题
【6月更文挑战第12天】ES6引入Promise和async/await解决异步问题。Promise处理异步操作,有pending、fulfilled、rejected三种状态,支持链式调用和并行处理。async/await是基于Promise的语法糖,使异步代码更同步化,提高可读性。两者都是处理回调地狱的有效工具,开发者应根据需求选择合适的方式。
57 3
|
7月前
|
监控 前端开发 JavaScript
等一下!深入async/await的异步世界
等一下!深入async/await的异步世界
107 1
|
7月前
|
JSON 前端开发 JavaScript
async/await语法
async/await语法
62 0
|
前端开发 JavaScript
async、await 实现原理
async、await 实现原理
83 1