[译]JavaScript Async/Await 秒杀 Promise 的6个理由(教学向)

原著:6 Reasons Why JavaScript's Async/Await Blows Promises Away (Tutorial)

作者:Mostafa Gaafar

译者:Tingrui Li

先说一件事,Node自从7.6版本之后已经原生支持async/await语法。如果你还未尝试过它,这里有一些例子告诉你为何你应当马上开始使用它,并不再回头。

Async/await 101

对于那些没有听说过这个玩意儿的人,这里有一些快速扫盲:

  • Async/await是编写异步代码新的方式,之前我们使用回调函数和Promise。
  • Async/await实际上是基于Promise的。它不能与常规回调函数和node回调函数一起使用。
  • Async/await跟Promise一样,不会阻塞程序。
  • Async/await让异步代码看起来更像同步执行的。这就是它强大的地方所在。

语法

假设一个方法 getJSON 返回一个Promise,这个Promise解析(resolve)出一个JSON对象,我们想要调用他并且打印那个JSON,返回"done"

你用Promise通常会这样实现:

const makeRequest = () =>
  getJSON()
    .then(data => {
      console.log(data)
      return "done"
    })

makeRequest()

而如果你用async/await:

const makeRequest = async () => {
  console.log(await getJSON())
  return "done"
}

makeRequest()

这里有一些区别:

  1. 我们的方法前面有一个关键字async。而await关键字只能被用于以async定义的方法内部。任何async方法都隐式地返回一个Promise,并且解析(resolve)的值是你从方法内return出来的东西(在这个例子中就是string "done")。

  2. 上面第一点表示我们不能在外层是用await,因为我们只能在async方法中使用它。

// 在外层不好使
// await makeRequest()

// 好使
makeRequest().then((result) => {
  // do something
})
  1. await getJSON()表示console.log会等到getJSON()这个Promise resolve之后才会打印它的值。

为什么这样更好?

1. 简洁、干净

瞧瞧我们少写了多少东西!就算是上面这么一个简短的例子,我们都明显少写了很多代码。不需要写.then,创建异步函数去处理response,或者对我们根本用不到的变量起data这个名字。我们也避免了把代码写成很多层。在下面的例子中,将会凸显这一点。

2. 错误处理

Async/await让我们终于可以以同一种方法同时处理同步和异步错误:经典的try/catch。在下面这个Promise的例子中,try/catch不会处理JSON.parse错误,因为那是在Promise中发生的。我们得在Promise中去.catch,然后重新写一次我们的错误处理代码,这可能比你的生产环境代码中的那些console.log要冗余的多。

const makeRequest = () => {
  try {
    getJSON()
      .then(result => {
        // JSON.parse可能会失败
        const data = JSON.parse(result)
        console.log(data)
      })
      // 处理JSON.parse可能抛出的错误
      .catch((err) => {
        console.log(err)
      })
  } catch (err) {
    console.log(err)
  }
}

现在我们用async/await写这段代码。catch代码块现在会处理JSON转换错误了。

const makeRequest = async () => {
  try {
    // JSON.parse可能会失败
    const data = JSON.parse(await getJSON())
    console.log(data)
  } catch (err) {
    console.log(err)
  }
}

是不是好棒棒?

3. 条件判断

想象一下这个场景,一段代码需要获取一些数据然后决定是返回这些数据,还是根据这些数据中的一些值来获取更多的数据。

const makeRequest = () => {
  return getJSON()
    .then(data => {
      if (data.needsAnotherRequest) {
        return makeAnotherRequest(data)
          .then(moreData => {
            console.log(moreData)
            return moreData
          })
      } else {
        console.log(data)
        return data
      }
    })
}

是不是看着就头大?这六层的代码很容易让你头晕目眩。那些括号,以及return语句只是为了将最终结果传递到主要Promise中。

这个例子用async/await重写以后可读性大大提高。

const makeRequest = async () => {
  const data = await getJSON()
  if (data.needsAnotherRequest) {
    const moreData = await makeAnotherRequest(data);
    console.log(moreData)
    return moreData
  } else {
    console.log(data)
    return data    
  }
}

4. 中间值

你可能遇到这种情况:你需要调用promise1然后用它的返回值去调用promise2,然后用两者的返回值去调用promise3,你的代码很可能是这样的:

const makeRequest = () => {
  return promise1()
    .then(value1 => {
      // do something
      return promise2(value1)
        .then(value2 => {
          // do something          
          return promise3(value1, value2)
        })
    })
}

如果promise3不需要value1,这个层级关系会清楚一些。如果你不能忍受,你可以用promise.all包装value1和value2:

const makeRequest = () => {
  return promise1()
    .then(value1 => {
      // do something
      return Promise.all([value1, promise2(value1)])
    })
    .then(([value1, value2]) => {
      // do something          
      return promise3(value1, value2)
    })
}

这种写法牺牲了可读性,value1value2不应当出于任何理由属于同一个数组。

同样的逻辑如果使用async/await来写会变得很简单,会让你怀疑以前为什么挣扎着让使用Promise的代码看起来更简单:

const makeRequest = async () => {
  const value1 = await promise1()
  const value2 = await promise2(value1)
  return promise3(value1, value2)
}

5. 错误栈

想像这样一个情景:连续链式调用多个Promise,然后其中某个地方可能会抛出异常:

const makeRequest = () => {
  return callAPromise()
    .then(() => callAPromise())
    .then(() => callAPromise())
    .then(() => callAPromise())
    .then(() => callAPromise())
    .then(() => {
      throw new Error("oops");
    })
}

makeRequest()
  .catch(err => {
    console.log(err);
    // 输出
    // Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)
  })

这个错误输出栈不能明确的指示错误发生在哪里。甚至会产生误导:整个错误只包含一个方法名then

然而,async/await的错误栈会指向具体产生错误的方法:

const makeRequest = async () => {
  await callAPromise()
  await callAPromise()
  await callAPromise()
  await callAPromise()
  await callAPromise()
  throw new Error("oops");
}

makeRequest()
  .catch(err => {
    console.log(err);
    // output
    // Error: oops at makeRequest (index.js:7:9)
  })

其实当你本地开发调试时,这一点意义不大。但是如果你需要在生产环境服务器上的代码上找错时,就非常实用了。在这种情况下,知道错误发生在makeRequest比知道错误发生在then.then.then.then...要好很多......

6. 调试

最后,async/await一个巨大的优势是它非常容易调试。对Promise进行调试出于两个原因非常的痛苦:

  1. 你不能在返回表达式的箭头函数上设置断点(没有函数体)。
const makeRequest = () =>{
reutrn callAPromise()
.then(()=>callAPromise())
.then(()=>callAPromise())
.then(()=>callAPromise())
.then(()=>callAPromise())
}
  1. 如果你在一个.then块内设置了断点,然后用step-over等功能时,调试器不会进入.then代码因为他只会"step"进同步代码。

使用async/await你不需要那么多箭头函数,你可以直接"step"那些await调用,就像普通的同步调用一样。

const makeRequest = async () =>{
  await callAPromise()
  await callAPromise()
  await callAPromise()
  await callAPromise()
  await callAPromise()
}

结论

Async/await是Javascript近几年来最具有革命性的特性之一,它让你体会到Promise是多么的混乱,然后给你一个方便的替代品。

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

推荐阅读更多精彩内容