• async函数


    一.什么是async函数

    1. 概念

    async关键字将函数转为异步函数。

    function hello() {
      return 'world';
    }
    hello(); //返回一个字符串
    
    // async关键字将函数转为异步函数;异步函数将普通函数转为promise
    async function hello() {
      return 'world';  
    }
    hello(); //返回一个promise对象

    await关键字只能用于异步函数中。

    async+await 等同于 generator+co

    await和yield不同在于,yield不返回任何值;await返回后面Promise对象的返回值或者返回后面的原始数据。

    var fnName = async function() {
      let result = await Promise.resolve(1);
      console.log(result); //1
    }
    fnName();
    // 相当于
    const co = require('co');
    co(function* fnName() {
      let result = yield Promise.resolve(1);
      console.log(result); //1
    })

    async函数相当于执行器+Generator函数,原理如下:

    async function fn() {
       //....
    }
    // 模拟源码实现
    function fn() {
      // run+Generator--等同于co + Generator
      return run(function* (){
    
      })
    }
    function run(fn) {
      return new Promise((resolve,reject) => {
        const gen = fn();
        function next(nextFn) {
          let next;
          try {
            next = nextFn(); //相当于所有的gen.next()方法在try代码块中执行
          } catch (error) {
            reject(error);
          }
          if (next.done) {
            resolve(next.value); // async函数生成的Promise对象的resolved状态是所有的await/yield命令完成, done:true时的值
          } 
          Promise.resolve(next.value).then(data => { // await后面不是Promise对象,会被当作Promise对象处理       
            next(function() {return gen.next(data)})// data的值即await返回的值,yield表达式的值
          }).catch(error => {
            next(function() {return gen.throw(error)})
          })
        }
        next(function() {return gen.next()}); // 不直接使用gen.next, 可能直接报错
      })
    }
    async原理

    2. 特征

    1. 比Generator函数的语法更语义化

    2. async函数执行返回一个Promise对象。co()方法也返回一个Promise对象

    返回的Promise对象状态会等所有await表达式运行完或者遇到return/throw后才会发生变化触发then方法。

    async函数的return值,作为async函数调用后生成的Promise对象的回调函数的参数

    var fnName = async function() {
      let result = await Promise.resolve(1);
      return 2;
    }
    const p = fnName();
    p.then(result => {
      console.log(result); // 2  等于done=true时的返回值;一般是return的值,否则undefined
    }).catch(() => {});

    应用:

    async函数返回Promise对象最直观的应用是作为map的回调函数

    const result = [1,2,3].map(async () => {
      await Promise.resolve('sth');
      return 5;
    });
    // map遍历后的值=回调函数的返回值
    // async函数的返回值是Promise对象 
    console.log('result-->',result);
    Promise.all(result).then(data => console.log('data-->',data))
    // 运行结果是
    result-->[Promise,Promise,Promise]
    data-->[5,5,5]
     

    3.await 后面应该是Promise对象;如果是原始类型/普通对象的值,直接返回。

      如果对象是thenable对象,直接将其当作Promise对象处理。

    4.await 等待后面的Promise对象返回结果。会拦截async函数内部的执行过程;async函数外部按照事件循环机制运行。

    5. 如果函数内部没有await命令;可以当作普通函数来确认执行顺序。

    3. 使用形式

    // 函数声明
    async function fn() {
      //await Promise对象
      // ...
    }
    // 函数表达式
    const fn = async function() {
      //...
    }
    // 对象方法
    const obj = {
      async fn() {
      }
    }
    // 类方法
    class A {
      constructor() {
      }
      async fn() {
    
      }
    }
    // 箭头函数
    const fn = async () => {}
    // 回调函数
    [1,2,3].map(async () => {
    // ...
    })

    4. 保留运行堆栈和上下文环境

    function c() {
      throw new Error('err')
    }
    // a,b函数的功能都是Promise改变状态之后运行c()
    function a() {
      const p = Promise.resolve(1);
      console.log('a');
      p.then(res => {
        console.log('a-->',res);
        c(); //执行到此处时,a()函数运行结束,上下文环境消失;错误堆栈不包含a
      })
    }
    async function b() {
      console.log('b');
      const res = await Promise.resolve(2);
      console.log('b-->',res);
      c(); //await暂停执行,保留上下文环境;错误堆栈会包含a
    }
    a();
    b();

    知识点: then方法可能会丢失上下文环境;await相当于调用then方法;会进入微任务队列

    运行结果如下:

    二. async函数生成的Promise对象的状态

    1. resolved状态

    1. 内部不抛出错误;且内部的Promise对象没有rejected状态

    根据原理代码可知,只有当内部遍历器的状态{value: returnValue, done: true}时,状态变为resolved状态,

    返回此时对应的value值,该值作为成功回调函数的参数。

    要想done=true,则所有的await对应的Promise对象都执行完成(源码内对应yield命令执行完成)。

    2. 内部抛出异常;但是进行了捕获;状态为resolved状态

    async function test() {
      try {
        console.log('--1--');
        await Promise.reject(1);
        console.log('--2--');
        await Promise.resolve(2);
      } catch (err) {
        console.log('err-->',err)
      }
      console.log('--3--');
    }
    test().then(result => {
      console.log('result-->',result);
    }).catch(err => {
      console.log('err-->',err)
    })
    // 运行结果如下:
    // --1--
    // err-->1
    // --3--
    // result-->undefined

    2. rejected状态

    只要async函数执行过程中出现一个未捕获的错误,整个Promise对象的状态是rejected。

    1. 函数内部手动抛出异常

    async function test() {
      throw new Error('err'); // throw自动触发状态为rejected
    }
    const p = test();
    p.then(
      data => console.log('data-->',data) //永不执行
    ).catch(
      err => console.log('err-->',err)
    )
    // 运行结果:
    err-->Error:err

    2. 函数内部所有await对应的Promise对象的状态只要出现一次rejected;

    就会抛出异常(参考原理)

    async function test() {
      console.log('--1--');
      await Promise.resolve('A');
      console.log('--2--');
      await Promise.reject('B'); //抛出异常,未捕获之后的代码不再执行
      console.log('--3--')
      await 'C';
    }
    test().then(result => {
      console.log('result-->',result); //不执行
    }).catch(err => {
      console.log('err-->',err);
    });
    // 运行结果如下
    --1--
    --2-- 
    err-->B

    所以为了代码的可执行性,await命令应该全部放在try...catch代码块中(推荐,一个try...catch就可以)。

    或者await后的Promise对象使用catch方法(不推荐,需要每个都写)

    三. 应用

    1. 实现继发(串行)异步操作--上一个结束再开始下一个

    async实现继发异步操作(最简单的实现方式)

    <!--
     * @Author: LyraLee
     * @Date: 2019-10-30 08:53:28
     * @LastEditTime: 2019-11-09 14:34:58
     * @Description: 串行异步操作
     -->
     <!DOCTYPE html>
     <html lang="en">
     
     <head>
       <meta charset="UTF-8">
       <meta name="viewport" content="width=device-width, initial-scale=1.0">
       <meta http-equiv="X-UA-Compatible" content="ie=edge">
       <title>串行异步操作</title>
       <style>
         #root {
            100px;
           height: 100px;
           background-color: #eee;
         }
       </style>
     </head>
     
     <body>
       <div id="root"></div>
       <script type="module">
         // 要求前一个动画执行完再执行下一个;有一个动画出错就不再执行;
         // 成功返回最后一个返回值;动画失败返回上一个执行成功的返回值
         function createAnimate(ele, color) {// 实现每一秒改变一次颜色;定时器同时执行,所以每次差1s
           return new Promise((resolve, reject) => {
             setTimeout(function () {
               ele.style.backgroundColor = color;
               resolve(color);
             }, 1000)
           })
         }
         // const animations = ['0f0','f00','00f'].map(i => createAnimate(i));
         // 上面的代码会预加载;相当于并发执行
         async function chainAnimationsPromise(ele, animations) {
           let result;
           for(let color of animations) {
             // 想实现异步任务继发执行,要在await后面再生成Promise对象
             // 因为Promise生成时立即执行;否则会提前执行。
             result = await createAnimate(ele, color);
           }
           return result;
         }
         
         const animations = ['#0f0','#f00','#00f'];
         const rootElement = document.querySelector('#root');
         chainAnimationsPromise(rootElement, animations).then(finalResult => {
           console.log('final-->', finalResult)
         });
       </script>
     </body>
     
     </html>
    View Code

    promise实现继发异步操作--代码逻辑比async复杂;代码量比async大

    <!--
     * @Author: LyraLee
     * @Date: 2019-10-30 08:53:28
     * @LastEditTime: 2019-11-09 14:31:09
     * @Description: 串行异步操作
     -->
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>串行异步操作</title>
      <style>
        #root {
           100px;
          height: 100px;
          background-color: #eee;
        }
      </style>
    </head>
    
    <body>
      <div id="root"></div>
      <script type="module">
        let createAnimate = function (elem, color) {
          return new Promise((resolve, reject) => {
            setTimeout(function () {    
              elem.style.backgroundColor = color;    
              resolve(color)
            }, 1000)
          })
        }
        function chainAnimationsPromise(elem, animations) {     
          let result = null;// 变量result用来保存上一个动画的返回值
          let p = Promise.resolve();
          for (let color of animations) {
            p = p.then(function (val) {
              result = val;
              return createAnimate(elem, color);
            });
          }
          return p.catch(function (e) {
            /* 忽略错误,继续执行 */
          }).then(function (res) { //res是最后的成功值,有错误时是undefined;result是失败后上次的成功返回值
            return res || result;
          });
        }
        
        const rootElement = document.querySelector('#root');
        const animations = ['#f00', '#0f0', '#00f']; 
        chainAnimationsPromise(rootElement, animations).then(finalResult => {
          console.log(finalResult)
        });
      </script>
    </body>
    
    </html>
    View Code

    2. 实现并发异步操作

    异步操作彼此之间独立,没有相互依赖关系,应该使用并发操作;可以大大降低执行时间。

    语法:

    async function fn() {
       await Promise.all([promise1, promise2,....]) //并发执行;彼此独立
    }

    示例(浏览器环境)

    function fetch() {
      return Promise.resolve({
        text() {
          return new Promise((resolve,reject) => {
            setTimeout(() => resolve('ok'),1000);
          });
        }
      })
    }
    
    const url1 = 'https://api.github.com/users/github/orgs';
    const url2 = 'https://api.github.com/users/ANTON072/subscriptions';
    const urls = [url1, url2];
    
    /***************************************
     * 并发执行
     * 执行结果之间彼此独立,互不依赖
     * 输出结果也没有顺序要求
     * ************************************/
    async function test() {
      console.time(2);
      await Promise.all(urls.map(url => fetch(url).then(res => res.text())))
      console.timeEnd(2)
    }
    console.time('1');
    test();  //主要是为了说明函数的执行时间是同步任务的执行时间
    console.timeEnd('1');

    执行结果如下:

    1: 0.18408203125ms
    2: 1000.796875ms

    比对继发执行的效果:

    /***************************************
     * 继发执行
     * 当前的执行结果依赖于上一次的执行结果
     * *************************************/
    async function test1() {
      console.time(3);
      for(let url of urls) { // 继发执行
        await fetch(url).then(res => res.text())
      }
      console.timeEnd(3)
    }
    test1();

    运行结果如下:

    3: 2003.61083984375ms

    由结果可知:并发执行快于继发执行。

    3. 实现按照顺序完成异步操作

    异步操作彼此之间没有依赖关系;但是要求输出结果按照某种顺序(入参顺序)

    /***************************************
     * 顺序执行--Promise实现
     * 执行结果之间彼此独立,
     * 但是输出结果要求按照入参顺序
     * *************************************/
    function test2() {
      // urls遍历时已经触发并发异步操作
      console.time(4);
      const promises = urls.map(url => fetch(url).then(res => res.text()));
      promises.reduce((demo, promise) => {
        console.log('reduce--')
        return demo.then(() => {
          console.log('--then promise--')
          return promise}).then(data => console.log('promise order-->',data))
      }, Promise.resolve()).then(() =>  console.timeEnd(4))
    }
    test2();
    
    /***************************************
     * 顺序执行--async函数实现
     * 执行结果之间彼此独立,
     * 但是输出结果要求按照入参顺序
     * *************************************/
    async function test3() {
      console.time(5);
      const promises = urls.map(url => fetch(url).then(res => res.text()));
      try {
        let result;
        for(let promise of promises) {
          console.log('for--')
          result = await promise;
          console.log('async order-->',result)
        }    
      } catch(error){
        console.log('err-->',error)
      }
      console.timeEnd(5)
    }
    test3();
    
    // 上面的log是为了联系方式异步方法的执行顺序;

    运行结果: 

    reduce--
    reduce--
    for--
    --then promise--
    promise order-->ok
    --then promise--
    promise order-->ok
    4: 1004.158935546875ms
    async order-->ok
    for--
    async order-->ok
    5:1004.30810546875ms

    虽然效果相似,但是async代码更简洁明了。

     四. 顶层await

    提案 : 目前浏览器支持;

    目的: 解决异步模块加载问题

    问题场景:

    // await.js 被加载模块代码如下
    let output;
    async function main() {
        const dynamic = await import(url1);
        const data = await fetch(url2);
        // output的值依赖上面结果;继发执行
        output = process(dynamic.default, data); 
    }
    main(); export { output }

    其他模块代码引用该文件,因为main是异步方法,所以引用时可能未执行完成;返回undefined

    现行解决方法:

    // async函数执行返回一个promise
    export default main();
    export {output}

    使用时:

    import promise, {output} from './await.js';
    
    promise.then(() => { //确保async函数执行完成
       //  使用output
    })

    使用顶层await解决方案:

    let output;
    const dynamic = import(url1);
    const data = fetch(url2);
    // output的值依赖上面结果;继发执行
    output = process(await(dynamic).default, await data); 
    export {output}

     应用:

    await imports('/.....')
  • 相关阅读:
    nopCommerce 2.60 之实现产品规格属性分组筛选
    chromedriver的使用
    linux 服务器发现了挖矿病毒
    C++day11 学习笔记
    C++day08 学习笔记
    C++day10 学习笔记
    C++day07 学习笔记
    C++day06 学习笔记
    C++day04 学习笔记
    C++day02 学习笔记
  • 原文地址:https://www.cnblogs.com/lyraLee/p/11803788.html
Copyright © 2020-2023  润新知