• async await 你真的用对了吗?


    大部分同学了解Promise,也知道async await可以实现同步化写法,但实际上对一些细节没有理解到位,就容易导致实际项目中遇到问题。
    开始先抛结论,下文将针对主要问题点进行论述。
    1、所有async方法调用,必须加await或catch,捕获错误(等待就用await,无需等待就用catch);如果最上层的async方法是被框架(react、egret)调用的,无法加await,则需要在这个async方法内做好try catch,不要把报错抛到框架层;
    2、async方法,实际返回了一个promise,默认把return值作为promise的resolve内容,而报错则封装为promise的reject;
    3、async方法内那么遇到异常要终止,可以直接throw ‘xxx’/Error;
    4、async方法内如果有调用下一层方法(这个方法是async方法或返回Promise),则需要加await,等待这个promise结果;如果同时要返回该下层调用的return值,则可以省略await,改为直接return这个Promise(但不建议,还是统一await同步写法比较好理解,详见下文例子);
    5、async方法如果正常执行,则直接执行完,return即可,不需要自行创建一层promise。 
     

    1. 为什么async方法一定要加await或catch?

    这里,需要先看一个例子,大家看看有什么问题。

    main();
    
    async function main() {
      try {
        loadImage();
        loadConfig();
      } catch (e) {
        console.log('main', e);
      }
    }
    
    function loadImage(){
      return new Promise((resolve, reject) => {
        setTimeout(reject, 1000, 'network error');
      });
    }
    
    async function loadConfig(){
      throw 'logic bug';
      await wait();
      console.log('config ok');
    }
    
    function wait(){
      return new Promise((resolve, reject) => {
        setTimeout(resolve, 1000);
      });
    }

    答案公布:

    无法捕获loadImage和loadConfig的报错。

    上述代码是一个典型,实际是从项目某个同学代码中抽象得来的。虽然看起来很工整很稳健,try catch做的很到位,但实际上,他没有把async和await理解透彻,没有理解到async返回的是Promise,无论是async内同步的报错还是异步(延迟)的报错,对上层调用来说,都是一个微任务。

    要解决上述问题,关键点就是,调用loadImage和loadConfig时,加await。

    async function main() {
      try {
        await loadImage();
        await loadConfig();
      } catch (e) {
        console.log('main', e);
      }
    }

    所以,调用async方法,不加await,就类似一个耍流氓行为,等同于使用Promise但不加catch。

    另外,最顶层的方法main再被调用时,由于没有包裹在async内,无法使用await,此时我们可以在main()后加上catch(),因为async方法实际返回的是Promise。题外话:目前top-level await还没有正式成为标准,但最新V8引擎里边已经可以使用(https://v8.dev/features/top-level-awaithttps://github.com/tc39/proposal-top-level-await

    2. 为什么async方法内不要return Promise?

    先看一个典型的例子

    async function main() {
      try {
        const result = await load(url);
        //...
      } catch (e) {
        console.error(e);
      }
    }
    
    async function load(url) {
      if (!url) {
        return Promise.reject('url is invalid');
      } else {
        const result = await fetch(url);  //代表一个异步操作
        return Promise.resolve(result);
      }
    }

    大家再看看这段代码是否有问题?

    答案公布:

    运行时,实际没有问题,逻辑是正常的,也能捕获错误。但是,有一些不足,多了一层Promise,会导致性能下降(新版本chrome解决了),而且影响回调执行时机。

    接下来通过两个代码对比一下,大家会更清楚。

    代码片段1

    console.log('script start');
    
    async function async1() {
      await async2();
      console.log('async1 end');
    }
    
    async function async2() {
      console.log('async2 end');
    }
    
    async1();
    setTimeout(function() {
      console.log('setTimeout');
    }, 0);
    new Promise(resolve => {
      console.log('Promise');
      resolve();
    }).then(function() {
      console.log('promise end');
    });
    console.log('script end');

    代码片段2

    console.log('script start');
    
    async function async1() {
      await async2();
      console.log('async1 end');
    }
    
    async function async2() {
      console.log('async2 end');
    return Promise.resolve().then(()=>{ console.log('async2 end in promise') }) } async1(); setTimeout(
    function() { console.log('setTimeout'); }, 0); new Promise(resolve => { console.log('Promise'); resolve(); }).then(function() { console.log('promise end'); }); console.log('script end');

    对比一下chrome控制台运行结果:

    左(片段1)   右(片段2)

        

    不同点就是,async1中await async2的时间推迟了,排在另外一个promise微任务之后。

    通过这例子可见,虽然async方法里边return一个Promise和直接return 值 并没有明显的差异,但会在调用时机上产生一些微妙的变化。

    所以,总体来说,不建议在async方法中再return或reject一个Promise。

    3. 参考写法

    最后,综合上述结论,提供一些参考写法,大家可以按需取用。

    main().catch(()=>{});   // 顶层调用,如果没有async包裹就用catch,如果是框架内调用,则在main函数体中做好catch
    
    async function main() {
      try {
        const result = await load(url);
        //...
      } catch (e) {
        // 所有try内的async方法均有await,所有错误都会层层抛出,直到这里捕获
        console.error(e);
      }
    }
    
    async function load(url) {
      if (!url) {
        throw 'url is invalid';  // 直接throw错误信息,简洁明了,直接中断后续流程
      }
    
      const config = await fetch(url);  // 假如fetch接口是一个网络获取,接收url,返回一个Promise
      return await runTask(config);  //代表一个异步操作
      // return runTask(config); // 和上一行,两种做法都可以,这里是return语句,可以把promise当做async方法的return值,上层await会解开。但为了方便记忆,不建议使用这个方式,应该统一使用await。
    }
    
    async function runTask(data) {
      // 对接一个不支持Promise的第三方库,我们只需要在最下层方法,包一个promise
      return new Promise((resolve, reject) => {
        thirdPartyRun(data, (res) => {
          resolve(res); // 这里返回数据
        }, (e) => {
          reject(e); // 这里可以做一些错误信息转换
        });
      });
    }
    
    // 代表一个不支持Promise的第三方库,如何对接到async await体系
    function thirdPartyRun(data, success, fail) {
      //...
    }
  • 相关阅读:
    Network (poj1144)
    C. Hongcow Builds A Nation
    ZYB loves Xor I(hud5269)
    D. Chloe and pleasant prizes
    Game(hdu5218)
    约瑟夫环的递推方法
    Misaki's Kiss again(hdu5175)
    Exploration(hdu5222)
    B. Arpa's weak amphitheater and Mehrdad's valuable Hoses
    C. Arpa's loud Owf and Mehrdad's evil plan
  • 原文地址:https://www.cnblogs.com/kenkofox/p/14010233.html
Copyright © 2020-2023  润新知