开源中国

我们不支持 IE 10 及以下版本浏览器

It appears you’re using an unsupported browser

为了获得更好的浏览体验,我们强烈建议您使用较新版本的 Chrome、 Firefox、 Safari 等,或者升级到最新版本的IE浏览器。 如果您使用的是 IE 11 或以上版本,请关闭“兼容性视图”。
Async/Await 优于 Promise 的 6 个理由 - 技术翻译 - 开源中国社区

Async/Await 优于 Promise 的 6 个理由 【已翻译100%】

oschina 推荐于 4个月前 (共 10 段, 翻译完成于 04-13) 评论 12
收藏  
54
推荐标签: 待读

NodeJS 从 7.6 版本开始已经内置了对 async/await 的支持,如果你错过了,还没用过该特性,这里有一些原因,来说明为什么你应该立即采用它。

Tocy
 翻译得不错哦!

Async/await 101

对于那些从未听说过这个话题的人来说,这是一个简单的介绍:

  • Async/await 是编写异步代码的新方式。以前的异步代码选项是回调和 promise。

  • Async/await 实际上是建立在 promise 之上的。它不能与普通回调或 node 回调一起使用。

  • Async/await,和 promise 相似,不能阻塞。

  • Async/await 使异步代码看起来更像一个同步代码。这就是它所有的能力所在。

Tocy
 翻译得不错哦!

语法

假设函数 getJSON 返回 Promise,而这个 Promise 由某个 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()

它们的区别在于

  • 我们的函数前有 async 关键字。await 关键字只能在定义为 async 的函数中使用。所有 async 函数都会隐式地返回 Promise,而函数的返回值将作为 Promise 兑现的值 (本例中是字符串 "done")。

  • 上一条意味着我们不能在顶层代码中使用 await,因为那不在任何 async 函数中。

// this will not work in top level
// await makeRequest()
// this will work
makeRequest().then((result) => {
  // do something
})
  •  await getJSON() 意思是 console.log 会在 getJSON() 返回的 Promise 兑现出返回的值时再进行打印。

边城
 翻译得不错哦!

为什么它更好呢?

1. 简明整洁

看看我们并没有写多少代码! 即使在上面的例子中,也很清楚明了,我们节省了大量的代码。 我们不必编写.then,不必创建一个匿名函数来处理响应,不必将命名数据传递给我们不需要使用的变量。我们也避免了嵌套代码。 这些小的优点叠加起来,这在下面的代码示例中将会变得更加明显。

Tocy
 翻译得不错哦!

2. 错误处理

Async/await 使得用同一种构造(古老但好用的 try/catch ) 处理同步和异步错误成为可能。在下面的 promises 示例中,因为错误发生在 promises 中,try/catch 将不会处理 JSON.parse 失败的情况。我们需要在 promise 中调用 .catch ,并复制我们的错误处理代码,这将可能比你的生产就绪代码中的 console.log 更复杂。

const makeRequest = () => {  try {
    getJSON()
      .then(result => {        // this parse may fail
        const data = JSON.parse(result)
        console.log(data)
      })      // uncomment this block to handle asynchronous errors
      // .catch((err) => {
      //   console.log(err)
      // })
  } catch (err) {
    console.log(err)
  }
}

现在看看相同的 async/await 调用代码。catch 模块现在可以处理 parsing 错误了。

const makeRequest = async () => {  try {    // this parse may fail
    const data = JSON.parse(await getJSON())
    console.log(data)
  } catch (err) {
    console.log(err)
  }
}
Tocy
 翻译得不错哦!

3. Conditionals

设想一下像下面的代码,它会获取一些数据,并决定是否应该返回该数据,或者根据数据中的某些值获取更多信息。

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
      }
    })
}

仅仅是看着这些就让你头疼了。很容易迷失在所有嵌套中(6级),大括号和返回语句中,而这些仅仅是在将最终结果传递给主 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    
  }
}
Tocy
 翻译得不错哦!

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 就不需要嵌套这么深。如果你是不喜欢深层嵌套的人,可以使用 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)
    })
}

这种方法会牺牲语义,降低可读性。把 value1 和 value2 放在一个数组里,除了避免嵌套 Promise,找不到其它任何理由。

使用 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);    // output
    // Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)
  })

从 Promise 链中返回的错误栈找不到一点错误发生位置的线索。更糟糕的是,它会产生误导;它的包含的唯一的函数名是 callPromise,但它与这个错误无关 (文件和行号仍然有用)。

不过,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 调用的哪一个里面好吧 ...

边城
 翻译得不错哦!

6. 调试

最后一点并非最不起眼,async/await 的杀手锏是易于调试。调试 Promise 总会有 2 点特别痛苦

1. 不能为箭头函数表达式设置断点 (没有函数体)。

(找个地方设置断点试试)

2. 如果在 .then 块中设置断点,然后使用调试快捷方式,比如跳过,调试器并不会移到下一个 .then 中,因为它是在代码中“步进”。

有了 async/await,你就不再需要这么多箭头函数,你可以在 await 调用中步进,就像普通的同步代码调用那样。

边城
 翻译得不错哦!

小结

async/await 是近几年来 JavaScript 引入的最具革命性的特性之一。它让人意识到 Promise 给语法带来的混乱,并提供了直观的替代方案。

相关事项

你在使用这个特性的时候可能会产生一些疑问

  • 它使异步代码不那么显眼:我已经习惯通过看到的回调或 .then 来判断异步代码,现在需要几周时间让眼睛习惯注意到新的迹象,不过 C# 拥有这个特性已经有几年了,所以熟悉这个特性的人知道这个短暂的小小的不便是值得付出的代价。

  • Node 7 并非 LTS(Long Time Support,长期支持) 版本:是的,不过下个月 Node 8 就来了,(现在使用的话),迁移到新版本就会非常轻松甚至不需要任何改变。

边城
 翻译得不错哦!
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们
评论(12)
Ctrl/CMD+Enter

早已在TS里用上……
唯一的缺点就是async/await报出异常信息比较坑。

引用来自“小翔”的评论

唯一的缺点就是async/await报出异常信息比较坑。
这一点没有办法,我用过的几个支持 Future Promise 的语言,都无法避免。当预先存储到堆的回调函数中发生错误时,是不能产生软件中断的,否则会导致应用程序的行为很难控制。一般是将这些错误作为(对象)值存储到堆中,由回调进行 read,然后在回调中由用户对错误进行控制。

即便是 async await 仅仅是语法糖,表面看是同步线程,实际上内部是在堆的表中存储回调,同时把回调相关 context 存储到堆中,在 poll 通知时从表中取出回调运行
Promise链式调用无法控制在中途中断执行

引用来自“小翔”的评论

唯一的缺点就是async/await报出异常信息比较坑。

引用来自“AutoPlus”的评论

这一点没有办法,我用过的几个支持 Future Promise 的语言,都无法避免。当预先存储到堆的回调函数中发生错误时,是不能产生软件中断的,否则会导致应用程序的行为很难控制。一般是将这些错误作为(对象)值存储到堆中,由回调进行 read,然后在回调中由用户对错误进行控制。

即便是 async await 仅仅是语法糖,表面看是同步线程,实际上内部是在堆的表中存储回调,同时把回调相关 context 存储到堆中,在 poll 通知时从表中取出回调运行
所以说,强大的type system还是很重要的(逃
c# 的味道
js啊
欢迎使用TypeScript来使用此特性。
C#早就有的语法糖了
谈什么优势啊 你async 不也是基于 promise :smile:
上次我用还是node6.2 ,用babel转的
还是不推荐在生产环境使用 :)
顶部