zoukankan      html  css  js  c++  java
  • 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) {
      //...
    }
  • 相关阅读:
    AJAX---发送GET请求并传递参数
    AJAX---遵循http协议
    AJAX---onreadystatechange事件中获取相应内容和readystate状态
    AJAX---发送请求
    AJAX---简介
    AJAX---学习roadmap
    jQuery---jquery.ui实现新闻模块
    jQuery---jquery.color.js和jquery.lazyload.js的使用
    jQuery---jQuery插件
    通过JavaScript调用SOAP终结点执行实体消息
  • 原文地址:https://www.cnblogs.com/kenkofox/p/14010233.html
Copyright © 2011-2022 走看看