async/await对比Promise优缺点分析

为什么都要2021年了,我还在对比这两套模式?

说实话,打算整理一下async/await的优缺点,看看到底在哪些场合适合使用async/await。结果搜到的很多对比文章,观点全是错的,也让我很意外,所以干脆来这么一篇吧。

我搜到了哪些错误的观点

  1. async/await整洁优雅到爆

加上try...catch...还那么优雅么??

  1. try...catch...能一股脑捕获同步和异步的错误,所以async/await牛逼。

一股脑捕获错误算哪门子优点!所有出错的可能性,都应当在原地避免,以及在原地try...catch...,而不是用一个大的try...catch...把所有可能出错的地方全管了。

  1. 说await是不阻塞的。

扯淡。

因为各种文章对async/await的缺点避而不谈,所以还是决定自己写一版优缺点分析。

async/await优点一:它做到了真正的串行的同步写法,代码阅读相对容易

这个优点是没错,但是JavaScript的百分之九十的异步场合都是ajax,ajax就一定需要考虑异常,很有可能需要try...catch...来处理异常,所以优势并不明显。

async/await优点二:对于条件语句和其他流程语句比较友好,可以直接写到判断条件里面

比如:

  function a() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(222)
      }, 2222)
    })
  };

  async function f() {
    try {
      if ( await a() === 222) {
        console.log('yes, it is!') // 会打印
      }
    } catch (err) {
      // ...
    }
  }

  f();

如果await a()没有出错的可能性,还可以省掉try...catch...。

async/await优点三:同样的,处理复杂流程时,在代码清晰度方面有优势

举个例子,有这样一套业务逻辑:有一个变量,类型是数组,如果它的length大于0,则遍历它进行下一步操作,如果length等于0,说明没有经历过ajax请求,则先ajax请求并赋值内容,然后再遍历它进行下一步操作;如果ajax的结果依旧是空,则显示toast,并中断流程。

  • Promise写法(伪代码):
  let arr = [];

  new Promise((resolve) => {
    if (arr.length) {
      resolve();
    } else {
      ajax().then((res) => {
        if (res.data.length) {
          arr = res.data;
          resolve();
        } else {
          showToast('数据为空')
        }
      })
    }
  }).then(() => {
    arr.forEach(() => {});
  })
  • async/await写法(伪代码):
  let arr = [];

  async function f() {
    if (!arr.length) {
      const res = await ajax();
      if (res.data.length) {
        arr = res.data;
      } else {
        showToast('数据为空')
        return;
      }
    }
    arr.forEach(() => {})
  }
  f();

可以看出:

  • Promise写法,必须有if (arr.length) {resolve();},而async/await写法不用考虑这个分支。

  • Promise写法的代码不仅冗长,而且这还是在省略了一部分代码的前提下,showToast('数据为空')这个分支永远是pending状态,可能会带来一些问题。

async/await无所谓优缺点的特点一:无法处理promise返回的reject对象,要借助try...catch...

await无法处理reject对象,比如:

  function g() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        reject(222)
      }, 2222)
    })
  };

  async function f() {
    const y = await g();
    console.log(y);
  }

  f();

await g()会直接报错,必须使用try...catch...捕获。

那么假定有3个ajax串行请求,Promise模式与async/await的对比如下:

  1. 允许统一处理reject的话:

Promise(伪代码):

ajax1().then(() => {
  console.log('ajax1 success')
  return ajax2();
}).then(() => {
  console.log('ajax2 success')
  return ajax3();
}).then(() => {
  console.log('ajax3 success')
}).catch((err) => {
  console.log('可能打印ajax1或2或3的fail', err);
})

async/await(伪代码):

async function a() {
  try {
    await ajax1();
    console.log('ajax1 success')
    await ajax2();
    console.log('ajax2 success')
    await ajax3();
    console.log('ajax3 success')
  } catch (err) {
    console.log('可能打印ajax1或2或3的reject', err);
  }
}

a()
  1. 需要单独处理每一个ajax的reject的话:

Promise(伪代码):

ajax1().then(() => {
  console.log('ajax1 success')
  return ajax2();
}, (err) => {
  console.log('ajax1的fail', err);
}).then(() => {
  console.log('ajax2 success')
  return ajax3();
}, (err) => {
  console.log('ajax2的fail', err);
}).then(() => {
  console.log('ajax3 success')
}, (err) => {
  console.log('ajax3的fail', err);
})

async/await(伪代码):

async function a() {
  try {
    await ajax1();
    console.log('ajax1 success')
  } catch (err) {
    console.log('打印ajax1的fail', err);
  }
  try {
    await ajax2();
    console.log('ajax2 success')
  } catch (err) {
    console.log('打印ajax2的fail', err);
  }
  try {
    await ajax3();
    console.log('ajax3 success')
  } catch (err) {
    console.log('打印ajax3的fail', err);
  }
}

a()

对比结果:从代码量上说,大同小异,就看你是否用的惯try...catch...。

为什么说用的惯,看这段代码,这段代码在上文贴过,想象一下,假如if ( await a() === 222) {的内容体有20行,会怎样——你会发现,trycatch相距22行,很远,难以阅读,而且,内容体里面如果还有try...catch...怎么办?这就成了try...catch...的嵌套圣诞树,更难以阅读,最终解决办法只能是:如果if的内容体太长,尤其是try...catch...的嵌套圣诞树,就放弃if ( await a() === 222) {这种优雅的写法,改成const res == await a();这种写法,然后把这句单独做try...catch...。

  function a() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(222)
      }, 2222)
    })
  };

  async function f() {
    try {
      if ( await a() === 222) {
        console.log('yes, it is!') // 会打印
      }
    } catch (err) {
      // ...
    }
  }

  f();

async/await无所谓优缺点的特点二、await只能串行,做不到并行

Promise可以轻松做到并行:

ajax1();
ajax2();

Promise.all([ajax1(), ajax2()])

但是await做不到,它一定是阻塞的。await甚至可以阻塞for循环:

  function g() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(222)
      }, 2222)
    })
  };

  async function f() {
    for (var i = 0; i < 10; i++) {
      const y = await g();
      console.log(y);
    }
  }

  f(); // 用时 22 秒才打印完

注意,await做不到并行,不代表async不能并行。只要await不在同一个async函数里就可以并行。比如:

  function g() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(222)
      }, 2222)
    })
  };

  function f() {
    [1,1,1,1,1,1,1,1,1,1].forEach(async (v) => {
      const y = await g();
      console.log(y);
    })
  }

  f();

async/await无所谓优缺点的特点三、全局捕获错误必须用window.onerror,不像Promise可以专用window.addEventListener('unhandledrejection', function),而window.onerror会捕获各种稀奇古怪的错误,造成系统浪费

尽管window.onerror的开销大,但是一个成熟的系统是一定要利用window.onerror做错误监控系统,所以,无所谓了。

async/await缺点一、try...catch...内部的变量无法传递给下一个try...catch...

Promise和then/catch内部定义的变量,能通过then链条的参数传递到下一个then/catch,但是async/await的try内部的变量,如果用letconst定义则无法传递到下一个try...catch...,只能在外层作用域先定义好。

async/await缺点二、async/await无法简单实现Promise的各种原生方法,比如.race()之类

如果真的编写一些工具库,确实可以实现Promise的各种原生方法,但放着Promise原生方法不用,却要写工具库,完全没必要。

什么场合用async/await,什么场合用Promise?

  1. 需要用到Promise各种便捷的方法(比如.race()之类)的时候,一定用Promise。

  2. 并行的请求最好用Promise。

  3. 不需要并行的场合,如果要传递参数,最好用Promise。

  4. 其他ajax场合,看你喜好try...catch...还是.catch(),以决定使用哪一方。

  5. 你必须看看下方对于拦截器的讨论。

额外讨论:ajax异常全部用拦截器处理,是否可以避免使用try...catch...?

拦截器对于后端业务代码出错,例如500错误,应当怎么处理呢?

如果:拦截器把200和500都归类到resolve

优点:

  • 共有的好处是只需要考虑200状态,所以确实不需要写try...catch...,也不需要.catch()

缺点:

  • 对两个方案都有缺点,500归为resolve的话,语义比较拧巴,而且业务代码里永远需要有这种代码:
if (res.code === 200) {

} else if (res.code === 500) {

}

如果:拦截器只将500错误归为reject,而200依然属于resolve

优点:

  • 共有的好处是不用一遍遍的写if (res.code === XXX),因为try里面是200的处理代码,catch里面是500的处理代码,天然就分开了。而且,500错误归为reject,从语义上说不拧巴。

缺点:

  • 对两个方案都有缺点,必须用try...catch...或.catch()捕获reject,不能省略。

结论

  1. 200和500全归到resolve的前提下,真的可以避免try...catch...,但是又带来了if (res.code === XXX)

  2. 如果喜欢写if (res.code === XXX),就让拦截器把200和500都归类到resolve,如果不喜欢写if (res.code === XXX),就500归类到reject。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,492评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,048评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,927评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,293评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,309评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,024评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,638评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,546评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,073评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,188评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,321评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,998评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,678评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,186评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,303评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,663评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,330评论 2 358