我最喜欢的网站之一是BerkshireHathaway.com--它简单,有效,并且自1997年推出以来一直正常运行。更值得注意的是,在过去的20年中,这个网站很有可能从未出现过错误。为什么?因为它都是静态的。它自20多年前推出以来几乎一样。如果你预先拥有所有数据,那么构建网站非常简单。不幸的是,现在大多数网站都没有。为了弥补这一点,我们发明了“模式”来处理为我们的应用程序提取外部数据。像大多数事情一样,这些模式都随着时间的推移而发生变化。在这篇文章中,我们将分析这三种最常见的模式的优缺点,模式分别是回调(Callbacks),Promises,和Async/Await 并从历史背景谈论它们的意义和进展。
让我们从这些数据获取的最初的模式开始,回调(Callbacks)
回调(Callbacks)
当我第一次学习编程时,它帮助我将函数理解为机器。这些机器可以做任何你想要的东西。他们甚至可以接受输入并返回一个值。每台机器上都有一个按钮,你可以在需要机器运行时按下该按钮,即()。
function add (x, y) {
return x + y
}
add(2,3) // 5 - 按下按钮,执行机器
无论我按下按钮,你按下按钮,或者别人按下按钮无所谓。无论何时按下按钮,机器都将运行。
function add (x, y) {
return x + y
}
const me = add
const you = add
const someoneElse = add
me(2,3) // 5 - Press the button, run the machine.
you(2,3) // 5 - Press the button, run the machine.
someoneElse(2,3) // 5 - Press the button, run the machine.
在上面的代码,我们分配add函数,三个不同的变量,me,you,和someoneElse。重要的是要注意add我们创建的原始变量和每个变量都指向内存中的相同位置。它们在不同的名称下完全相同。所以,当我们调用me时you,或者someoneElse,就好像我们正在调用一样add函数。
现在如果我们把add机器送到另一台机器怎么办?请记住,按下()按钮并不重要,如果按下它,它就会运行。
function add (x, y) {
return x + y
}
function addFive (x, addReference) {
return addReference(x, 5) // 15 - Press the button, run the machine.
}
addFive(10, add) // 15
你的大脑可能在这一点上有点奇怪,但这里没有新的东西。我们不是“按下按钮” add,而是add作为参数传递addFive,重命名它addReference,然后我们“按下按钮”或调用它。
这突出了JavaScript语言的一些重要概念。首先,正如你可以将字符串或数字作为参数传递给函数一样,你也可以将函数的引用作为参数传递。当执行此操作时,作为参数传递的函数称为回调函数,并且将回调函数传递给的函数称为高阶函数。
因为词汇很重要,所以这里的代码与重新命名的变量相同,以匹配他们演示的概念。
function add (x,y) {
return x + y
}
function higherOrderFunction (x, callback) {
return callback(x, 5)
}
higherOrderFunction(10, add)
这种模式应该看起来很熟悉,无处不在。如果你曾经使用过任何JavaScript Array方法,那么你已经使用了回调。如果你曾经使用过lodash,那么你已经使用过回调。如果你曾经使用过jQuery,那么你已经使用了回调。
[1,2,3].map((i) => i + 5)
_.filter([1,2,3,4], (n) => n % 2 === 0 );
$('#btn').on('click', () =>
console.log('Callbacks are everywhere')
)
通常,回调有两种常见的用例。第一,我们看下.map和_.filter例子,是翻转一个值到另一个很好的抽象。我们说“嘿,这是一个数组和一个函数。来吧,根据我给你的函数给我一个新的值“。第二个,也就是我们在jQuery示例中看到的,是将函数的执行延迟到特定时间。“嘿,这是这个函数。每当btn点击具有id的元素时,请继续调用它。“这是我们将关注的第二个用例,”延迟执行函数直到特定时间“。
现在我们只看了同步的例子。正如我们在本文开头所讨论的那样,我们构建的大多数应用程序都没有预先获得所需的所有数据。相反,他们需要在用户与应用程序交互时获取外部数据。我们刚刚看到回调如何成为一个很好的用例,因为它们再次允许你“延迟执行函数直到特定时间”。看看我们如何使该句子适应数据提取并不需要太多想象力。我们可以延迟函数的执行,直到我们获得所需的数据,而不是将函数的执行延迟到特定时间。这可能是最流行的例子,jQuery的方法:getJSON。
// updateUI and showError are irrelevant.
// Pretend they do what they sound like.
const id = 'tylermcginnis'
$.getJSON({
url: `https://api.github.com/users/${id}`,
success: updateUI,
error: showError,
})
在获得用户数据之前,我们无法更新应用的UI。那么我们该怎么办?我们说,“嘿,这是一个对象。如果请求成功,请继续调用success并传递用户的数据。如果没有,请继续调用error并传递错误对象。你不需要担心每种方法的作用,只要确保在你应该的时候调用它们。这是使用异步请求回调的完美演示。
在这一点上,我们已经了解了回调是什么以及它们如何在同步和异步代码中都有用处的。我们还没有谈到的是回调的黑暗面。请看下面的代码。你能说出发生了什么吗?
// updateUI, showError, and getLocationURL are irrelevant.
// Pretend they do what they sound like.
const id = 'tylermcginnis'
$("#btn").on("click", () => {
$.getJSON({
url: `https://api.github.com/users/${id}`,
success: (user) => {
$.getJSON({
url: getLocationURL(user.location.split(',')),
success (weather) {
updateUI({
user,
weather: weather.query.results
})
},
error: showError,
})
},
error: showError
})
})
如果觉得有帮助,你可以在这里玩实时版本。
请注意,我们添加了一些回调层。首先,我们说在btn点击具有id的元素之前不要运行初始的AJAX请求。单击按钮后,我们会发出第一个请求。如果该请求成功,我们会发出第二个请求。如果该请求成功,我们将调用updateUI从两个请求获得的数据的方法。无论你是否乍一看是否理解了代码,客观地说它比以前的代码更难阅读。这将我们带到“回调地狱”的主题。
作为人类,我们很自然地会顺序思考。当你在嵌套回调中嵌套回调时,它会强迫你超出你自然的思维方式。当你的软件阅读方式与自然思考方式之间存在脱节时,就会发生错误。
像大多数软件问题的解决方案一样,一种使“回调地狱”更容易消费的常用方法是模块化你的代码。
function getUser(id, onSuccess, onFailure) {
$.getJSON({
url: `https://api.github.com/users/${id}`,
success: onSuccess,
error: onFailure
})
}
function getWeather(user, onSuccess, onFailure) {
$.getJSON({
url: getLocationURL(user.location.split(',')),
success: onSuccess,
error: onFailure,
})
}
$("#btn").on("click", () => {
getUser("tylermcginnis", (user) => {
getWeather(user, (weather) => {
updateUI({
user,
weather: weather.query.results
})
}, showError)
}, showError)
})
如果觉得有帮助,你可以在这里玩实时版本。
好的,函数名称可以帮助我们更加了解正在发生的事情,但客观上是“更好”吗?并不是很多。我们只是在回调地狱的可读性问题上加了一个创可贴。问题仍然存在,我们自然地按顺序思考,即使有额外的功能,嵌套的回调也会使我们摆脱顺序的思维方式。
下一期回调与控制反转有关。当你编写一个回调时,假设你给回调的程序是负责的,并且会在它应该的时候(并且只有当它)时调用它。实际上是将程序控制权转换为另一个程序。当您处理jQuery,lodash甚至vanilla JavaScript等库时,可以安全地假设使用正确的参数在正确的时间调用回调函数。但是,对于许多第三方库,回调函数是您与它们交互方式的接口。第三方库无论是故意的还是偶然的,都可以打破他们与你的回调互动的方式,这是完全合情合理的。
function criticalFunction () {
// It's critical that this function
// gets called and with the correct
// arguments.
}
thirdPartyLib(criticalFunction)
既然你不是那个调用者criticalFunction,你就可以控制调用它的时间和参数。大多数时候这不是问题,但是当它出现问题时,这是一个很大的问题。
Promises
你有没有预订去过一个繁忙的餐馆?当这种情况发生时,餐厅需要一种方法在桌子打开时与你联系。从历史上看,当你的桌子准备就绪时,他们只会取你的名字并大喊大叫。然后,自然而然地,他们决定开始变幻想。一个解决方案是,一旦桌子打开,他们就会取你的号码并给你发短信,而不是取你的名字。这使您可以超出大喊大叫的范围,但更重要的是,它允许他们随时根据需要定位你的手机广告。听起来有点熟?这应该!好吧,也许不应该。这是回调的隐喻!将你的号码提供给餐馆就像给第三方服务提供回拨功能一样。你希望餐厅在桌子打开时给您发短信,就像你一样期望第三方服务在何时以及如何表达时调用你的功能。一旦你的号码或回叫功能掌握在他们手中,您就失去了所有控制权。
值得庆幸的是,存在另一种解决方案。一个设计,允许您保持所有控制。你甚至可能以前都经历过 - 这是他们给你的小嗡嗡声。你知道,这个。
如果你之前从未使用过,那么这个想法很简单。他们没有取你的名字或号码,而是给你这个设备。当设备开始嗡嗡作响并发光时,你的桌子就准备好了。当你等待桌子打开时,你仍然可以做任何你想做的事,但现在你不必放弃任何东西。事实上,恰恰相反。他们必须给你一些东西。没有控制倒置。
蜂鸣器始终处于三种不同状态中的一种- pending,fulfilled或rejected。
pending是默认的初始状态。当他们给你蜂鸣器时,它处于这种状态。
fulfilled 当蜂鸣器闪烁并且你的桌子准备就绪时蜂鸣器所在的状态。
rejected当出现问题时,蜂鸣器处于状态。也许餐厅即将关闭,或者他们忘了有人在晚上出租餐厅。
同样,要记住的重要一点是,你,蜂鸣器的接收器,拥有所有的控制权。如果蜂鸣器进入fulfilled,你可以去你的桌子。如果它被放入fulfilled并且你想忽略它,那么很酷,你也可以这样做。如果它被放入rejected,那很糟糕,但你可以去别的地方吃。如果没有任何事情发生并且它留在pending,你永远不会吃,但你实际上并没有任何东西。
现在你已成为餐厅蜂鸣器的主人,让我们将这些知识应用到重要的事情上。
如果给餐厅你的号码就像给他们一个回调功能,接收这个小小的东西就像收到所谓的“Promise”。
一如既往,让我们从为什么开始吧。为什么Promises存在?它们的存在使得使异步请求更易于管理的复杂性。完全像蜂鸣器,一个 Promise可以处于三种状态之一pending,fulfilled或者rejected。与蜂鸣器不同,它们代表表示餐馆桌子状态的这些状态,它们代表异步请求的状态。
如果异步请求仍在进行中,则Promise状态为pending。如果异步请求成功完成,则Promise状态将更改为fulfilled。如果异步请求失败,Promise则将更改为状态rejected。蜂鸣器比喻很有意义,对吗?
既然你已经理解了Promise存在的原因以及它们可以存在的不同状态,那么我们还需要回答三个问题。
1、如何创造一个Promise?
2、如何改变Prommise的状态?
3、当Promise的状态发生变化时,如何监听?
如何创造一个Promise?
这个很直接。创建一个new实例Promise。
const promise = new Promise()
如何改变Prommise的状态?
该Promise构造函数接受一个参数,一个(回调)函数。这个函数将传递两个参数,resolve和reject。
resolve - 一个允许你更改Promise状态的功能 fulfilled
reject- 一个允许你更改Promise状态的功能rejected。
在下面的代码中,我们使用setTimeout等待2秒然后调用resolve。这将改变Promise的状态fulfilled。
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve() // Change status to 'fulfilled'
}, 2000)
})
我们可以通过在创建它之后立即记录promise来看到这种变化,然后resolve在调用之后大约2秒后再次记录。
注意Promise从pending到resolved。
当Promise的状态发生变化时,如何监听?
在我看来,这是最重要的问题。很酷我们知道如何创建Promise并改变其状态,但如果我们在状态发生变化后不知道如何做任何事情,那就毫无价值。
我们还没有谈到的一件事是Promise实际上是什么。当你创建一个时new Promise,你真的只是创建一个普通的旧JavaScript对象。该对象可以调用两个方法then,和catch。这是关键。当promise的状态更改fulfilled为时,.then将调用传递给的函数。当promise的状态更改rejected为时,.catch将调用传递给的函数。这意味着一旦你创建了一个promise,如果异步请求成功,你将传递你想要运行的函数.then。如果异步请求失败,你将传递要运行的功能.catch。
我们来看一个例子吧。我们将setTimeout再次使用fulfilled在两秒钟(2000毫秒)之后将Promise的状态更改为。
function onSuccess () {
console.log('Success!')
}
function onError () {
console.log('💩')
}
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, 2000)
})
promise.then(onSuccess)
promise.catch(onError)
如果运行上面的代码,你会注意到大约2秒后,您将在控制台中看到“成功!”。这种情况再次发生的原因是两件事。首先,当我们创建了promise时,我们resolve在〜2000毫秒之后调用- 这改变了promise的状态fulfilled。其次,我们将onSuccess函数传递给promises的.then方法。通过这样做,我们告诉Promise,onSuccess当Promise的状态改变为fulfilled〜2000毫秒后它所做的时,调用。
现在让我们假装发生了一些不好的事情,我们想要改变Promise的状态rejected。resolve我们打电话,而不是打电话reject。
function onSuccess () {
console.log('Success!')
}
function onError () {
console.log('💩')
}
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject()
}, 2000)
})
promise.then(onSuccess)
promise.catch(onError)
现在这一次而不是onSuccess被调用的onError函数,因为我们调用了函数reject。
你已经了解了Promise API的方法,让我们开始查看一些真实的代码。
还记得我们之前看到的最后一个异步回调示例吗?
function getUser(id, onSuccess, onFailure) {
$.getJSON({
url: `https://api.github.com/users/${id}`,
success: onSuccess,
error: onFailure
})
}
function getWeather(user, onSuccess, onFailure) {
$.getJSON({
url: getLocationURL(user.location.split(',')),
success: onSuccess,
error: onFailure,
})
}
$("#btn").on("click", () => {
getUser("tylermcginnis", (user) => {
getWeather(user, (weather) => {
updateUI({
user,
weather: weather.query.results
})
}, showError)
}, showError)
})
我们可以在这里使用Promise API而不是使用回调吗?如果我们将AJAX请求包含在promise中,该怎么办?然后我们可以简单地resolve或reject取决于请求的方式。让我们开始吧getUser。
function getUser(id) {
return new Promise((resolve, reject) => {
$.getJSON({
url: `https://api.github.com/users/${id}`,
success: resolve,
error: reject
})
})
}
很好。请注意,参数getUser已更改。而不是接收id,onSuccess和onFailure,它只是接收id。不再需要那些其他两个回调函数,因为我们不再反转控制。相反,我们使用Promise resolve和reject函数。resolve如果请求成功reject将被调用,如果有错误将被调用。
接下来让我们重构一下getWeather。我们将遵循相同的策略。我们将使用使用resolve和reject而不是接收onSuccess和onFailure回调函数。
function getWeather(user) {
return new Promise((resolve, reject) => {
$.getJSON({
url: getLocationURL(user.location.split(',')),
success: resolve,
error: reject,
})
})
}
看起来不错。现在我们需要更新的最后一件事是我们的点击处理程序。请记住,这是我们想要采取的流程。
从Github API获取用户的信息。
1、使用用户的位置从Yahoo Weather API获取他们的天气。
2、使用用户信息及其天气更新UI。
3、让我们从#1开始 - 从Github API获取用户的信息。
$("#btn").on("click", () => {
const userPromise = getUser('tylermcginnis')
userPromise.then((user) => {
})
userPromise.catch(showError)
})
请注意,现在它不是getUser接受两个回调函数,而是返回一个我们可以调用.then和.catch启用的Promise。如果.then被调用,将使用用户的信息调用它。如果.catch被调用,它将被调用错误。
接下来让我们做#2 - 使用用户的位置来获取他们的天气。
$("#btn").on("click", () => {
const userPromise = getUser('tylermcginnis')
userPromise.then((user) => {
const weatherPromise = getWeather(user)
weatherPromise.then((weather) => {
})
weatherPromise.catch(showError)
})
userPromise.catch(showError)
})
请注意,我们遵循我们在#1中完全相同的模式,但现在我们调用getWeather它传递给user我们的对象userPromise。
最后,#3 - 使用用户信息及其天气更新UI。
$("#btn").on("click", () => {
const userPromise = getUser('tylermcginnis')
userPromise.then((user) => {
const weatherPromise = getWeather(user)
weatherPromise.then((weather) => {
updateUI({
user,
weather: weather.query.results
})
})
weatherPromise.catch(showError)
})
userPromise.catch(showError)
})
这是你可以使用的完整代码。
新代码看起来更好,但我们仍然可以做出一些改进。在我们做出这些改进之前,你需要知道Promise到两个功能:链式调用和将参数从resolve传递到then。
链式调用
.then和.catch都会返回一个新的Promise。这似乎是一个小细节,但它很重要,因为它意味着Promise可以被链式调用。
在下面的示例中,我们调用getPromise它返回一个将在至少2000毫秒内解析的promise。从那里,因为.then将返回一个Promise,我们可以继续将我们的.thens链接在一起,直到我们抛出一个new Error被该.catch方法捕获的东西。
function getPromise () {
return new Promise((resolve) => {
setTimeout(resolve, 2000)
})
}
function logA () {
console.log('A')
}
function logB () {
console.log('B')
}
function logCAndThrow () {
console.log('C')
throw new Error()
}
function catchError () {
console.log('Error!')
}
getPromise()
.then(logA) // A
.then(logB) // B
.then(logCAndThrow) // C
.catch(catchError) // Error!
很酷,但为什么这么重要?请记住,在回调部分,我们谈到了回调的一个缺点,即它们会迫使你摆脱自然,顺序的思维方式。当你将Promise链接在一起时,它并不会强迫你摆脱那种自然的思维方式,因为链式调用Promise是连续的。getPromise runs then logA runs then logB runs then...。
这样你就可以看到另一个示例,这是使用fetchAPI 时的常见用例。fetch将返回一个将通过HTTP响应解决的Promise。要获得实际的JSON,你需要调用.json。由于链接,我们可以按顺序思考这个问题。
fetch('/api/user.json')
.then((response) => response.json())
.then((user) => {
// user is now ready to go.
})
现在我们知道有关链式调用,让我们来重构我们早期getUser/ getWeather代码并且使用它。
function getUser(id) {
return new Promise((resolve, reject) => {
$.getJSON({
url: `https://api.github.com/users/${id}`,
success: resolve,
error: reject
})
})
}
function getWeather(user) {
return new Promise((resolve, reject) => {
$.getJSON({
url: getLocationURL(user.location.split(',')),
success: resolve,
error: reject,
})
})
}
$("#btn").on("click", () => {
getUser("tylermcginnis")
.then(getWeather)
.then((weather) => {
// We need both the user and the weather here.
// Right now we just have the weather
updateUI() // ????
})
.catch(showError)
})
它看起来好多了,但现在我们遇到了一个问题。你能发现它吗?在第二个.then我们要调用updateUI。问题是,我们需要通过updateUI这两个user和weather。目前我们如何设置,我们只收到weather,而不是user。不知何故,我们需要找出一种方法来使它成为一个Promise,即getWeather使用user和来解决回报weather。
这是关键。resolve只是一个功能。您传递给它的任何参数都将传递给给定的函数.then。这是什么意思是,里面getWeather,如果我们调用resolve我们自己,我们可以通过它weather和user。然后,.then我们链中的第二个方法将同时接收user和weather作为参数。
function getWeather(user) {
return new Promise((resolve, reject) => {
$.getJSON({
url: getLocationURL(user.location.split(',')),
success(weather) {
resolve({ user, weather: weather.query.results })
},
error: reject,
})
})
}
$("#btn").on("click", () => {
getUser("tylermcginnis")
.then(getWeather)
.then((data) => {
// Now, data is an object with a
// "weather" property and a "user" property.
updateUI(data)
})
.catch(showError)
})
你可以在这里玩最终的代码
它位于我们的点击处理程序中,与回调相比,你真正看到了Promise的力量。
// Callbacks 🚫
getUser("tylermcginnis", (user) => {
getWeather(user, (weather) => {
updateUI({
user,
weather: weather.query.results
})
}, showError)
}, showError)
// Promises ✅
getUser("tylermcginnis")
.then(getWeather)
.then((data) => updateUI(data))
.catch(showError);
遵循这种逻辑感觉很自然,因为它是我们习惯于按顺序思考的方式。getUser then getWeather then update the UI with the data。
现在很明显,promises会大大提高异步代码的可读性,但有没有办法让它变得更好?假设你是TC39委员会成员,并且你有能力为JavaScript语言添加新功能。你将采取哪些步骤来改进此代码?
$("#btn").on("click", () => {
getUser("tylermcginnis")
.then(getWeather)
.then((data) => updateUI(data))
.catch(showError)
})
正如我们所讨论的那样,代码读得非常好。正如我们的大脑工作一样,它是按顺序排列的。我们遇到的一个问题是我们需要将data(users)从第一个异步请求一直到最后一个.then。这不是什么大不了的事,但它让我们改变了我们的getWeather功能,也传递了它users。如果我们编写异步代码的方式与编写同步代码的方式相同怎么办?如果我们这样做了,那么这个问题就会彻底消失,而且它仍会按顺序读取。这是一个想法。
$("#btn").on("click", () => {
const user = getUser('tylermcginnis')
const weather = getWeather(user)
updateUI({
user,
weather,
})
})
好吧,那会很好。我们的异步代码看起来就像我们的同步代码。我们的大脑没有额外的步骤需要采取,因为我们已经非常熟悉这种思维方式。可悲的是,这显然是行不通的。如你所知,如果我们要运行上面的代码,user并且weather两者都只是Promise,因为那是什么getUser并getWeather返回。但请记住,我们正在使用TC39。我们有能力为我们想要的语言添加任何功能。按原样,这段代码在制作工作时会非常棘手。我们必须以某种方式教JavaScript引擎,以便了解异步函数调用和常规的同步函数调用之间的区别。让我们在代码中添加一些关键字,以便在引擎上更轻松。
首先,让我们在主函数本身添加一个关键字。这可以让引擎知道这个函数内部我们将要进行一些异步函数调用。让我们用async它。
$("#btn").on("click", async () => {
const user = getUser('tylermcginnis')
const weather = getWeather(user)
updateUI({
user,
weather,
})
})
酷。这看似合理。接下来让我们添加另一个关键字,让引擎确切知道被调用的函数何时是异步的并且将返回一个promise。我们来使用await。如同,“嘿发动机。此函数是异步的并返回一个promise。不要像往常一样继续,继续“等待”Promise的最终的值并在继续之前将其返回“。与这两个我们的新的async和await在游戏中的关键字,我们的新的代码看起来像这样。
$("#btn").on("click", async () => {
const user = await getUser('tylermcginnis')
const weather = await getWeather(user.location)
updateUI({
user,
weather,
})
})
非常漂亮。我们已经发明了一种合理的方法来使我们的异步代码看起来和行为就像它是同步的一样。现在下一步是让TC39上的某个人相信这是一个好主意。幸运的是,正如你现在可能已经猜到的那样,我们不需要做任何令人信服的事情,因为这个功能已经成为JavaScript的一部分而且它被称为Async/Await。
不相信我?这是我们的实时代码,现在我们已经添加了Async / Await。随意玩它。
异步函数返回一个promise
既然您已经看到了Async / Await的好处,那么让我们讨论一些重要的细节,这些细节很重要。首先,无论何时添加async到函数,该函数都将隐式返回一个promise。
async function getPromise(){}
const promise = getPromise()
即使getPromise字面上是空的,它仍然会返回一个Promise,因为它是一个async函数。
如果async函数返回一个值,那么该值也将包含在一个promise中。这意味着你将不得不使用.then它来访问它。
async function add (x, y) {
return x + y
}
add(2,3).then((result) => {
console.log(result) // 5
})
没有异步的等待很糟糕
如果你尝试await在非函数内部使用关键字async,则会出现错误。
$("#btn").on("click", () => {
const user = await getUser('tylermcginnis') // SyntaxError: await is a reserved word
const weather = await getWeather(user.location) // SyntaxError: await is a reserved word
updateUI({
user,
weather,
})
})
以下是我对此的看法。添加async到函数时,它会执行两项操作。它使得函数本身返回(或包装返回的内容)一个Promise并使它可以await在其内部使用
错误处理
你可能已经注意到我们有点作弊。在我们的原始代码中,我们有一种方法可以捕获任何错误.catch。当我们切换到Async / Await时,我们删除了该代码。使用Async / Await,最常见的方法是将代码包装在一个try/catch块中以便能够捕获错误。
$("#btn").on("click", async () => {
try {
const user = await getUser('tylermcginnis')
const weather = await getWeather(user.location)
updateUI({
user,
weather,
})
} catch (e) {
showError(e)
}
})
(完)
链接
后记
以上译文仅用于学习交流,水平有限,难免有错误之处,敬请指正。