添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
英勇无比的脸盆  ·  django - Could not ...·  1 年前    · 
叛逆的泡面  ·  EXPORTS | Microsoft Learn·  1 年前    · 
强健的吐司  ·  formily2 源码学习 - 掘金·  1 年前    · 

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

为什么需要 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) 执行 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 执行完成并返回 其实也可以和 事件循环 结合在一起看,下面给出了简单的图解:
  • 公众号 @「熊的猫」
    粉丝