美文网首页
[译]JavaScript Async/Await 秒杀 Pro

[译]JavaScript Async/Await 秒杀 Pro

作者: 卷蜀黍丶 | 来源:发表于2017-07-14 14:57 被阅读0次

原著: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是多么的混乱,然后给你一个方便的替代品。

相关文章

网友评论

      本文标题:[译]JavaScript Async/Await 秒杀 Pro

      本文链接:https://www.haomeiwen.com/subject/esznhxtx.html