在为网络编写代码时,您最终需要执行一些可能需要花费几分钟才能完成的进程。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 🌮s
> Here are your 🌮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 上。
尝试、捕获、最终
最终,我们希望将错误状态添加到此过程中。为此,我们有便捷的 try
、catch
和 finally
代码块。
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
,因为我们并不总是需要它。它是一个无论成功还是失败都会始终执行的代码块。如果要在 try
和 catch
中重复内容,请考虑使用 finally
。我通常将其用于一些清理工作。如果您想了解更多信息,我写了一篇关于此内容的文章。
您最终可能需要更复杂的错误处理,例如取消异步函数的方法。不幸的是,目前还没有方法可以原生实现这一点,但幸运的是,Kyle Simpson 创建了一个名为 CAF 的库,可以提供帮助。
进一步阅读
Async/Await 的解释通常从回调开始,然后是 Promise,并使用这些解释来构建 Async/Await。由于 Async/Await 现在得到了很好的支持,因此我们没有完成所有这些步骤。这仍然是一个很好的背景信息,特别是如果您需要维护旧的代码库。以下是我最喜欢的资源
- 异步 JavaScript:从回调到 Promise 再到 Async/Await(Tyler McGinnis)
- 使用 async/await 进行异步 JavaScript(Marius Schulz)
- 掌握异步 JavaScript(James K. Nelson)
感谢您 - 一如既往的好文章,以非常清晰的方式解释了前端开发的重要部分 :-)
这可能有点不透明,因为我没有得到解释参考……但……
有一次,我问了一整屋子的程序员关于在 async/await 中使用 try catch 代码块的问题,绝大多数人都说……哇!不要这样做。你不会想要那个的。
当时没有机会问为什么,因为演示继续进行。
使用 try catch 代码块是否有任何弊端?
这听起来很疯狂,因为在生产应用中,您必须处理这些潜在的错误。
嘿,丹!
在
await
周围使用try…catch
是绝对可以的,也是语言特性设计中预期的行为。这正是为什么await
在被拒绝的 Promise 上会本地抛出错误的原因。与往常一样,实际的问题更多是:您是否确实有可以在本地完成的相关错误处理,还是应该让错误传递到调用者堆栈?这正是为什么
return await
仅被认为适合在try…catch
中使用的原因:如果您确实想要在本地处理拒绝,那么await
就有意义。否则,return await
是没有必要的,因为没有额外的本地代码需要运行,因此您不妨直接返回原始 Promise,而无需用您的函数是async
创建的额外层进行包装。本文代码中唯一一个
return await
语句落入了这个陷阱:该函数完全可以不使用await
也不必是async
,它可以使用一个 Promise 而不是两个,并且性能会略微提升……而且代码更少 ;-)很多人更喜欢在调用者上使用它。例如 iAmAsyncAndCouldThrow.catch(error => …)
async/await 和 Promise 比嵌套回调方便得多,但我仍然有时会在嵌套的异步函数和循环中迷路。这主要发生在我处理多个 API 调用和数据处理时。
喜欢这个!把它添加到 React Native Now #58
https://reactnativenow.com/issues/58#start
莎拉:
很棒的文章。它帮助我更好地理解了 Promise/async/await。
但是我在代码示例中发现了一些错误,导致它们无法正常工作。
在 getQuote() 中(简化版本):
fetch 引用相对路径
fetch(
quotes.json
)fetch(
programming-quotes.com/${quoteType.type}
)当我将其更改为完整路径时,它们就可以工作。
fetch(
https://s3-us-west-2.amazonaws.com/s.cdpn.io/28963/quotes.json
)fetch(
https://programming-quotes-api.herokuapp.com/quotes/${quoteType.type}
)