了解 Async Await

Avatar of Sarah Drasner
Sarah Drasner

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 $200 免费信用额度!

在为网络编写代码时,您最终需要执行一些可能需要花费几分钟才能完成的进程。JavaScript 无法真正进行多任务处理,因此我们需要一种方法来处理这些长时间运行的进程。

Async/Await 是一种处理这种基于时间的排序的方法。当您需要进行某种网络请求然后使用结果数据时,它特别有用。让我们深入研究吧!

承诺?承诺。

Async/Await 是一种 Promise。在 JavaScript 中,Promises 是可以有多种状态的对象(有点像现实生活中的人 ☺️)。Promises 会这样做,因为有时我们要求的东西不会立即可用,我们需要能够检测它的状态。

想象一下,有人让你承诺为他们做某事,比如帮他们搬家。他们请求时,处于初始状态。但是在你出现并帮助他们搬家之前,你还没有履行对他们的承诺。如果你取消了计划,你就拒绝了这个承诺。

类似地,JavaScript 中 Promise 的三种可能状态是

  • pending:当您第一次调用 Promise 并且不知道它将返回什么时。
  • fulfilled:表示操作已成功完成
  • rejected:操作失败

以下是一个 Promise 在这些状态下的示例

这是 fulfilled 状态。我们将名为 getSomeTacos 的 Promise 存储起来,传入 resolve 和 reject 参数。我们告诉 Promise 已解析,这允许我们随后再进行两次控制台日志。

const getSomeTacos = new Promise((resolve, reject) => {
  console.log("Initial state: Excuse me can I have some tacos");

  resolve();
})
  .then(() => {
    console.log("Order some tacos");
  })
  .then(() => {
    console.log("Here are your tacos");
  })
  .catch(err => {
    console.error("Nope! No tacos for you.");
  });
> Initial state: Excuse me can I have some tacos
> Order some tacos
> Here are your tacos

查看 CodePen 中的
Promise 状态
,作者是 Sarah Drasner (@sdras)
CodePen 上。

如果我们选择 rejected 状态,我们将执行相同的函数,但这次将其拒绝。现在,控制台中将打印出来的是 Initial State 和 catch error

const getSomeTacos = new Promise((resolve, reject) => {
  console.log("Initial state: Excuse me can I have some tacos");

  reject();
})
  .then(() => {
    console.log("Order some tacos");
  })
  .then(() => {
    console.log("Here are your tacos");
  })
  .catch(err => {
    console.error("Nope! No tacos for you.");
  });
> Initial state: Excuse me can I have some tacos
> Nope! No tacos for you.

当我们选择 pending 状态时,我们将简单地 console.log 我们存储的内容,即 getSomeTacos。这将打印出 pending 状态,因为这是我们记录它时 Promise 所处的状态!

console.log(getSomeTacos)
> Initial state: Excuse me can I have some 🌮s
> Promise {<pending>}
> Order some &#x1f32e;s
> Here are your &#x1f32e;s

那之后呢?

但这是我一开始感到困惑的一部分。要从 Promise 中获取值,您必须使用 .then() 或其他返回 Promise 解析结果的方法。如果您仔细思考一下,这很合理,因为您需要捕获它最终将是什么——而不是它最初是什么——因为它最初将处于 pending 状态。这就是为什么当我们记录上面的 Promise 时,它会打印出 Promise {<pending>}。在那时,还没有任何东西解析。

Async/Await 实际上只是您刚刚看到的那些 Promise 上的语法糖。以下是一个简单的示例,说明我如何将其与 Promise 一起使用来安排多个执行。

async function tacos() {
  return await Promise.resolve("Now and then I get to eat delicious tacos!")
};

tacos().then(console.log)

或者是一个更深入的示例

// this is the function we want to schedule. it's a promise.
const addOne = (x) => {
  return new Promise(resolve => {
    setTimeout(() => { 
      console.log(`I added one! Now it's ${x + 1}.`)
      resolve()
    }, 2000);
  })
}

// we will immediately log the first one, 
// then the addOne promise will run, taking 2 seconds
// then the final console.log will fire
async function addAsync() {
  console.log('I have 10')
  await addOne(10)
  console.log(`Now I'm done!`)
}

addAsync()
> I have 10
> I added one! Now it's 11.
> Now I'm done!

查看 CodePen 中的
Async 示例 1
,作者是 Sarah Drasner (@sdras)
CodePen 上。

一件事 (a)waits 另一件事

Async/Await 的一个常见用途是将其用于链接多个异步调用。在这里,我们将获取一些 JSON,我们将用它来传递到我们的下一个 fetch 调用中,以确定要从第二个 API 中获取哪种类型的东西。在本例中,我们要访问一些编程笑话,但首先我们需要从另一个 API 中找出我们想要哪种类型的引用。

第一个 JSON 文件看起来像这样 - 我们希望引用类型为 random

{
  "type": "random"
}

鉴于我们刚刚获得的 random 查询参数,第二个 API 将返回类似以下内容的东西

{
  "_id":"5a933f6f8e7b510004cba4c2",
  "en":"For all its power, the computer is a harsh taskmaster. Its programs must be correct, and what we wish to say must be said accurately in every detail.",
  "author":"Alan Perlis",
  "id":"5a933f6f8e7b510004cba4c2"
}

我们调用 async 函数,然后让它等待检索第一个 .json 文件,然后再从 API 中获取数据。一旦发生这种情况,我们就可以对该响应做一些操作,比如将其添加到页面中。

async function getQuote() {
  // get the type of quote from one fetch call, everything else waits for this to finish
  let quoteTypeResponse = await fetch(`https://s3-us-west-2.amazonaws.com/s.cdpn.io/28963/quotes.json`)
  let quoteType = await quoteTypeResponse.json()

    // use what we got from the first call in the second call to an API, everything else waits for this to finish
  let quoteResponse = await fetch("https://programming-quotes-api.herokuapp.com/quotes/" + quoteType.type)
  let quote = await quoteResponse.json()

  // finish up
  console.log('done')
}

我们甚至可以使用模板字面量和箭头函数简化它

async function getQuote() {
  // get the type of quote from one fetch call, everything else waits for this to finish
  let quoteType = await fetch(`quotes.json`).then(res => res.json())

    // use what we got from the first call in the second call to an API, everything else waits for this to finish
  let quote = await fetch(`programming-quotes.com/${quoteType.type}`).then(res => res.json())

  // finish up
  console.log('done')
}

getQuote()

这是一个关于此过程的动画解释。

查看 CodePen 中的
Async Await 的动画描述
,作者是 Sarah Drasner (@sdras)
CodePen 上。

尝试、捕获、最终

最终,我们希望将错误状态添加到此过程中。为此,我们有便捷的 trycatchfinally 代码块。

try {
  // I’ll try to execute some code for you
}
catch(error) {
  // I’ll handle any errors in that process
} 
finally {
  // I’ll fire either way
}

让我们重新构造上面的代码,以使用此语法并捕获任何错误。

async function getQuote() {
  try {
    // get the type of quote from one fetch call, everything else waits for this to finish
    let quoteType = await fetch(`quotes.json`).then(res => res.json())

      // use what we got from the first call in the second call to an API, everything else waits for this to finish
    let quote = await fetch(`programming-quotes.com/${quoteType.type}`).then(res => res.json())

    // finish up
    console.log('done')
  }

  catch(error) {
    console.warn(`We have an error here: ${error}`)
  }
}

getQuote()

我们在这里没有使用 finally,因为我们并不总是需要它。它是一个无论成功还是失败都会始终执行的代码块。如果要在 trycatch 中重复内容,请考虑使用 finally。我通常将其用于一些清理工作。如果您想了解更多信息,我写了一篇关于此内容的文章

您最终可能需要更复杂的错误处理,例如取消异步函数的方法。不幸的是,目前还没有方法可以原生实现这一点,但幸运的是,Kyle Simpson 创建了一个名为 CAF 的库,可以提供帮助

进一步阅读

Async/Await 的解释通常从回调开始,然后是 Promise,并使用这些解释来构建 Async/Await。由于 Async/Await 现在得到了很好的支持,因此我们没有完成所有这些步骤。这仍然是一个很好的背景信息,特别是如果您需要维护旧的代码库。以下是我最喜欢的资源