Promise在JavaScript中的工作原理——全面的新手指南
異步指令不會(huì)阻止 JavaScript 引擎主動(dòng)接受和處理更多的指令。這就是 JavaScript 本質(zhì)上是非阻塞的原因。
JavaScript 中有一些異步特性,其中之一就是Promises。要使用 promises,您必須采用一種特殊的語(yǔ)法,使編寫(xiě)異步指令更有條理。使用 promises 是每個(gè) JavaScript 開(kāi)發(fā)人員都應(yīng)該學(xué)習(xí)的一項(xiàng)非常有用的技能。
本文是 JavaScript 中 promises 的深入指南。您將了解為什么 JavaScript 有 promise,什么是 promise,以及如何使用它。您還將學(xué)習(xí)如何使用 async/await(一種源自 promises 的特性)以及什么是作業(yè)隊(duì)列。
以下是我們將涵蓋的主題:
你為什么要關(guān)心承諾?
什么是承諾?
如何在 JavaScript 中創(chuàng)建承諾
如何將回調(diào)附加到承諾
如何處理承諾中的錯(cuò)誤
如何一次處理多個(gè) promise
什么是異步/等待語(yǔ)法?
如何在 JavaScript 中創(chuàng)建異步函數(shù)
如何使用 await 關(guān)鍵字
如何處理異步/等待中的錯(cuò)誤
什么是作業(yè)隊(duì)列?
本指南有望成為一本有趣且富有洞察力的讀物。:) 它適用于希望更好地編寫(xiě) JavaScript 異步指令的任何人,從而正確利用該語(yǔ)言所提供的功能。有了所有這些,讓我們開(kāi)始吧。
(更多優(yōu)質(zhì)內(nèi)容:java567.com)
先決條件
為了跟隨材料并掌握它,這里有一些你應(yīng)該具備的東西:
JavaScript 基礎(chǔ)知識(shí)
了解 JavaScript 如何處理異步操作
了解這些主題將幫助您正確理解您將要學(xué)習(xí)的內(nèi)容。如果你沒(méi)有先決條件,你可以去學(xué)習(xí)它們并返回。本文將在此處使用這些主題中的一些概念。
為什么要關(guān)心 Promise?
Promises 并不總是 JavaScript 的一部分。回調(diào)與異步函數(shù)一起工作以產(chǎn)生過(guò)去所需的結(jié)果。回調(diào)是作為異步函數(shù)參數(shù)的任何函數(shù),異步函數(shù)調(diào)用它來(lái)完成其操作。
要調(diào)用異步函數(shù),您必須像這樣將回調(diào)作為參數(shù)傳遞:
?function callback(result) {
? ?// Use the result from the Async operation
?}
?
?randomAsyncOperation((response) => callback(response));
但是回調(diào)有一個(gè)很大的問(wèn)題。演示問(wèn)題可以使理解更容易。
假設(shè)您有一個(gè)異步函數(shù)可以在互聯(lián)網(wǎng)上的某個(gè)地方獲取數(shù)據(jù)。這個(gè)函數(shù)應(yīng)該接受兩個(gè)回調(diào),successCallback和failureCallback。
successCallback如果操作成功并且程序找到了適當(dāng)?shù)馁Y源,則 將運(yùn)行。但是failureCallback如果操作不成功并且找不到資源,它將運(yùn)行。
?function SuccessCallback(result) {
? ?console.log("Resource found", result);
?}
?
?function failureCallback(error) {
? ?console.error("Ooops. Something went wrong", error);
?}
要運(yùn)行異步函數(shù),您必須將兩個(gè)回調(diào)函數(shù)作為參數(shù)傳遞:
?fetchResource(url, successCallback, failureCallback)
這里,url是一個(gè)代表資源位置的變量。
這段代碼現(xiàn)在可以順利運(yùn)行。您已經(jīng)處理了函數(shù)可能遇到的兩種可能情況。您有成功操作的回調(diào)和失敗操作的回調(diào)。
現(xiàn)在假設(shè)您要執(zhí)行許多其他提取操作,但每個(gè)操作都必須成功才能運(yùn)行下一個(gè)操作。如果您需要的數(shù)據(jù)必須按特定順序出現(xiàn)并且不能分散,這將很有用。
例如,如果下一個(gè)操作的結(jié)果取決于前一個(gè)操作的結(jié)果,您可能會(huì)遇到這種情況。
在這種情況下,您的成功回調(diào)將有自己的成功回調(diào),這很重要,因?yàn)槿绻Y(jié)果進(jìn)來(lái),您需要使用它們。
?fetchResource(
? ?url,
? ?function (result) {
? ? ?// Do something with the result
? ? ?fetchResource(
? ? ? ?newUrl,
? ? ? ?function (result) {
? ? ? ? ?// Do something with the new result
? ? ? ? ?fetchResource(
? ? ? ? ? ?anotherUrl,
? ? ? ? ? ?function (result) {
? ? ? ? ? ? ?// Do something with the new result
? ? ? ? ? ?},
? ? ? ? ? ?failureCallback
? ? ? ? ?);
? ? ? ?},
? ? ? ?failureCallback
? ? ?);
? ?},
? ?failureCallback
?);
從示例中,您可能會(huì)注意到出現(xiàn)了復(fù)雜情況。您必須在failureCallback每次調(diào)用異步函數(shù)時(shí)重復(fù)嵌套成功回調(diào)。
這些嵌套的回調(diào)導(dǎo)致了“厄運(yùn)回調(diào)金字塔”或回調(diào)地獄,它很快就會(huì)變成一場(chǎng)噩夢(mèng)。有沒(méi)有更有效的方法來(lái)處理這種情況?
JavaScript 引入了 Promises 作為ES6(ES2015)的一部分來(lái)解決這個(gè)問(wèn)題。它簡(jiǎn)化了回調(diào)的使用,并提供了更好的語(yǔ)法,您很快就會(huì)看到?,F(xiàn)在,Promise 是開(kāi)發(fā)人員在 JavaScript 中使用的大多數(shù)現(xiàn)代異步操作的基礎(chǔ)。
什么是承諾?
圖片來(lái)源: https: //gifer.com
承諾是對(duì)將來(lái)會(huì)發(fā)生某事的保證或保證。一個(gè)人可以向另一個(gè)人承諾一個(gè)特定的結(jié)果或結(jié)果。承諾不限于個(gè)人,政府和組織也可以做出承諾。你之前可能已經(jīng)做出了承諾。
這種保證(承諾)帶來(lái)兩種可能的結(jié)果——實(shí)現(xiàn)或失敗。承諾與表明它已實(shí)現(xiàn)的結(jié)果相關(guān)聯(lián)。如果那個(gè)結(jié)果沒(méi)有發(fā)生,那么承諾就失敗了。最后的承諾必須具有這些結(jié)果之一。
在 JavaScript 中,Promise 是一個(gè)對(duì)象,它將在未來(lái)某個(gè)時(shí)間產(chǎn)生一個(gè)單一的值。如果承諾成功,它將產(chǎn)生一個(gè)已解決的值,但如果出現(xiàn)問(wèn)題,它將產(chǎn)生承諾失敗的原因。這里的可能結(jié)果類似于現(xiàn)實(shí)生活中的承諾。
JavaScript 承諾可以處于三種可能狀態(tài)之一。這些狀態(tài)表示承諾的進(jìn)度。他們是:
pending:這是已定義承諾的默認(rèn)狀態(tài)
fulfilled:這是承諾成功的狀態(tài)
rejected:這是一個(gè)失敗的承諾的狀態(tài)
一個(gè) promise 從pending到fulfilled,或者從pending到rejected —— 'fulfilled' 和 'rejected' 表示一個(gè) promise 的結(jié)束。
從現(xiàn)在開(kāi)始,本文將把“承諾”稱為 JavaScript 對(duì)象。
如何在 JavaScript 中創(chuàng)建一個(gè) Promise
要?jiǎng)?chuàng)建一個(gè)承諾,您需要使用Promise構(gòu)造函數(shù)創(chuàng)建一個(gè)實(shí)例對(duì)象。構(gòu)造Promise函數(shù)接受一個(gè)參數(shù)。該參數(shù)是一個(gè)函數(shù),用于定義何時(shí)解析新承諾,以及何時(shí)拒絕它。
?const promise = new Promise((resolve, reject) => {
? ?// Condition to resolve or reject the promise
?});
例如,假設(shè)您希望承諾在兩秒超時(shí)后解決。您可以通過(guò)將其寫(xiě)入構(gòu)造函數(shù)的參數(shù)來(lái)實(shí)現(xiàn)。
?const promise = new Promise((resolve, reject) => {
? ?setTimeout(() => resolve("Done!"), 2000);
?});
在 promises 中,resolve是一個(gè)帶有可選參數(shù)的函數(shù),表示已解析的值。此外,reject是一個(gè)帶有可選參數(shù)的函數(shù),表示承諾失敗的原因。在上面的示例中,promise 的解析值是 string 'Done!'。
這是另一個(gè)示例,顯示如何根據(jù)您設(shè)置的條件解決或拒絕承諾。在此示例中,承諾的結(jié)果基于程序生成的隨機(jī)數(shù)。
?const promise = new Promise((resolve, reject) => {
? ?const num = Math.random();
? ?if (num >= 0.5) {
? ? ?resolve("Promise is fulfilled!");
? ?} else {
? ? ?reject("Promise failed!");
? ?}
?});
從這些示例中,您可以看到您可以控制何時(shí)解決或拒絕您的承諾,并將其與特定條件聯(lián)系起來(lái)。至此,您已經(jīng)了解了如何在 JavaScript 中創(chuàng)建承諾。
如何將回調(diào)附加到 Promise
要為承諾創(chuàng)建回調(diào),您需要使用.then()方法。此方法接受兩個(gè)回調(diào)函數(shù)。如果 promise 得到解決,則第一個(gè)函數(shù)運(yùn)行,而如果 promise 被拒絕,則第二個(gè)函數(shù)運(yùn)行。
?const promise = new Promise((resolve, reject) => {
? ?const num = Math.random();
? ?if (num >= 0.5) {
? ? ?resolve("Promise is fulfilled!");
? ?} else {
? ? ?reject("Promise failed!");
? ?}
?});
?
?function handleResolve(value) {
? ?console.log(value);
?}
?
?function handleReject(reason) {
? ?console.error(reason);
?}
?
?promise.then(handleResolve, handleReject);
?// Promise is fulfilled!
?// or
?// Promise failed!
這是處理您承諾的可能結(jié)果的方式。您的承諾中任何未處理的錯(cuò)誤最終都會(huì)使它們處于拒絕狀態(tài),但已處理的錯(cuò)誤會(huì)使操作返回已履行的承諾。
可以創(chuàng)建一個(gè)立即解析的承諾,然后使用該.then()方法附加一個(gè)回調(diào)。你也可以用同樣的方式創(chuàng)建一個(gè)立即被拒絕的承諾。
?Promise.resolve("Successful").then((result) => console.log(result));
?// Successful
?
?Promise.reject("Not successful").then((result) => console.log(result));
?// Error: Uncaught (in promise)
被拒絕的承諾中的錯(cuò)誤是因?yàn)槟枰x一個(gè)單獨(dú)的回調(diào)來(lái)處理被拒絕的承諾。
?Promise.reject("Not successful").then(
? ?() => {
? ? ?/*Empty Callback if Promise is fulfilled*/
? ?},
? ?(reason) => console.error(reason)
?);
?// Not Successful
現(xiàn)在您已經(jīng)正確處理了被拒絕的結(jié)果。
Promises 使得鏈接異步指令變得異常容易。當(dāng)您使用該方法處理承諾時(shí).then(),該操作總是返回另一個(gè)承諾。通過(guò)采用這種方法,您可以消除前面提到的“厄運(yùn)回調(diào)金字塔”。
考慮之前導(dǎo)致金字塔結(jié)構(gòu)的代碼:
?fetchResource(
? ?url,
? ?function (result) {
? ? ?// Do something with the result
? ? ?fetchResource(
? ? ? ?newUrl,
? ? ? ?function (result) {
? ? ? ? ?// Do something with the new result
? ? ? ? ?fetchResource(
? ? ? ? ? ?anotherUrl,
? ? ? ? ? ?function (result) {
? ? ? ? ? ? ?// Do something with the new result
? ? ? ? ? ?},
? ? ? ? ? ?failureCallback
? ? ? ? ?);
? ? ? ?},
? ? ? ?failureCallback
? ? ?);
? ?},
? ?failureCallback
?);
但是,因?yàn)?then()返回另一個(gè)承諾,所以這就是如何用承諾編寫(xiě)上面相同的指令:
?fetchResource(url)
? ?.then(handleResult, failureCallback)
? ?.then(handleNewResult, failureCallback)
? ?.then(handleAnotherResult, failureCallback);
如您所見(jiàn),調(diào)用 promises 不需要嵌套語(yǔ)法。您甚至可以消除重復(fù)項(xiàng)failureCallback以使代碼更整潔,這是本文接下來(lái)的部分將探討的內(nèi)容。
如何處理 Promise 中的錯(cuò)誤
要處理 Promises 中的錯(cuò)誤,請(qǐng)使用.catch()方法。如果您的任何承諾出現(xiàn)任何問(wèn)題,此方法可以捕獲該錯(cuò)誤的原因。
?Promise.reject(new Error()).catch((reason) => console.error(reason));
?// Error
這一次在我們的示例中,錯(cuò)誤輸出不再是“未捕獲”的,因?yàn)?catch().
.catch()您還可以在承諾鏈中使用該方法。它捕獲它在鏈中遇到的第一個(gè)錯(cuò)誤。
例如,按照f(shuō)etchResource()上一節(jié)示例中的功能重構(gòu)承諾鏈。這就是您如何停止代碼中的錯(cuò)誤回調(diào)重復(fù)。
?fetchResource(url)
? ?.then(handleResult)
? ?.then(handleNewResult)
? ?.then(handleAnotherResult)
? ?.catch(failureCallback);
在繼續(xù)進(jìn)行進(jìn)一步的異步操作之前,您還可以使用它.catch()來(lái)檢查一組 promise 中的錯(cuò)誤。
?fetchResource(url)
? ?.then(handleResult)
? ?.then(handleNewResult)
? ?.catch(failureCallback)
? ?// Check for Errors in the above group of promises before proceeding
? ?.then(handleAnotherResult);
該.catch()方法無(wú)需嵌套錯(cuò)誤回調(diào)函數(shù)即可解決承諾中的任何錯(cuò)誤。
要將異步操作鏈接到承諾,而不管承諾是否已解決,請(qǐng)使用該.finally()方法。該.then()方法是您如何處理承諾的結(jié)果,為解決和拒絕編寫(xiě)單獨(dú)的條件。.catch()僅在出現(xiàn)錯(cuò)誤時(shí)運(yùn)行。但有時(shí)您可能希望無(wú)論先前的承諾發(fā)生什么情況,操作都會(huì)運(yùn)行。
Usingfinally()有助于防止.then()和中可能出現(xiàn)的代碼重復(fù).catch()。它用于無(wú)論是否有錯(cuò)誤都必須運(yùn)行的操作。
?fetchResource(url)
? ?.then(handleResult)
? ?.then(handleNewResult)
? ?.finally(onFinallyHandle);
該finally()方法在實(shí)際應(yīng)用中有一些用例。如果您想對(duì) promise 發(fā)起的活動(dòng)執(zhí)行清理操作,這一點(diǎn)很重要。前端 Web 應(yīng)用程序的另一個(gè)用例是更新用戶界面,例如停止加載微調(diào)器。
如何一次處理多個(gè) Promise
一次可以運(yùn)行多個(gè)承諾。到目前為止,您看到的所有示例都是針對(duì)一個(gè)接一個(gè)運(yùn)行的承諾。
在前面的例子中,promises 的運(yùn)行類似于同步代碼,因?yàn)樗鼈兊却耙粋€(gè)代碼被解決或拒絕。但是你可以有多個(gè)并行運(yùn)行的承諾。
以下是可以幫助我們實(shí)現(xiàn)這一目標(biāo)的可用方法:
Promise.all()
Promise.race()
Promise.any()
Promise.allSettled()
在本文的這一部分,我們將回顧這些方法。
方法Promise.all()_
Promise.all()接受一組承諾作為參數(shù),但返回單個(gè)承諾作為輸出。如果輸入數(shù)組中的所有承諾都得到滿足,它返回的單個(gè)承諾將解析為一個(gè)值數(shù)組。數(shù)組Promise.all()resolve with 將包含輸入數(shù)組中各個(gè)承諾的解析值。
?const promise1 = Promise.resolve(`First Promise's Value`);
?const promise2 = new Promise((resolve) =>
? ?setTimeout(resolve, 3000, `Second Promise's Value`)
?);
?const promise3 = new Promise((resolve) =>
? ?setTimeout(resolve, 2000, `Third Promise's Value`)
?);
?
?Promise.all([promise1, promise2, promise3]);
?
?// Output on the console
?
?// *Promise {<fulfilled>: Array(3)}*
?
?Promise.all([promise1, promise2, promise3]).then((values) => {
? ?values.forEach((value) => console.log(value));
?});
?
?// Output on the console
?
?// First Promise's Value
?// Second Promise's Value
?// Third Promise's Value
如果輸入數(shù)組中至少有一個(gè)承諾沒(méi)有解決,Promise.all()將返回一個(gè)被拒絕的承諾并說(shuō)明原因。拒絕的原因與輸入數(shù)組中第一個(gè)被拒絕的承諾相同。
?const promise1 = Promise.resolve(`First Promise's Value`);
?const promise2 = new Promise((resolve, reject) =>
? ?setTimeout(reject, 3000, `First reason for rejection`)
?);
?const promise3 = new Promise((resolve, reject) =>
? ?setTimeout(reject, 2000, `Second reason for rejection`)
?);
?
?Promise.all([promise1, promise2, promise3]);
?
?// Output on the console
?
?// *Promise {<rejected>: "First reason for rejection"}*
Promise.all()將在返回值之前運(yùn)行所有輸入承諾。但它不會(huì)一個(gè)接一個(gè)地運(yùn)行承諾——而是同時(shí)運(yùn)行它們。
這就是為什么返回一個(gè)值所花費(fèi)的總時(shí)間Promise.all()大約是數(shù)組中最長(zhǎng)的承諾完成所花費(fèi)的時(shí)間。
盡管如此,它必須在返回任何內(nèi)容之前完成所有的承諾。
方法Promise.race()_
Promise.race()接受一組承諾作為參數(shù),并返回一個(gè)承諾作為輸出。它返回的單個(gè)承諾是完成運(yùn)行的最快承諾——無(wú)論是否已解決。這意味著Promise.race()將返回承諾數(shù)組中執(zhí)行時(shí)間最短的承諾。
?const promise1 = new Promise((resolve) =>
? ?setTimeout(resolve, 3000, `First Promise's Value`)
?);
?const promise2 = new Promise((resolve) =>
? ?setTimeout(resolve, 2000, `Second Promise's Value`)
?);
?const promise3 = Promise.resolve(`Third Promise's Value`);
?
?Promise.race([promise1, promise2, promise3]);
?
?// Output on the console
?
?// *Promise {<fulfilled>: "Third Promise's Value"}*
在上面的示例中,因?yàn)閜romise3是一個(gè)在創(chuàng)建時(shí)解決的承諾,所以Promise.race()它以最快的速度返回。就像Promise本文在本節(jié)中討論的其他方法一樣,它并行運(yùn)行承諾,而不是一個(gè)接一個(gè)地運(yùn)行。
如果執(zhí)行時(shí)間最短的 promise 碰巧被拒絕了,Promise.race()則返回一個(gè)被拒絕的 promise 以及最快的 promise 被拒絕的原因。
?const promise1 = Promise.reject(`Reason for rejection`);
?const promise2 = new Promise((resolve) =>
? ?setTimeout(resolve, 3000, `First resolved Promise`)
?);
?const promise3 = new Promise((resolve) =>
? ?setTimeout(resolve, 2000, `Second resolved Promise`)
?);
?
?Promise.race([promise1, promise2, promise3]);
?
?// Output on the console
?
?// *Promise {<rejected>: "Reason for rejection"}*
Promise.race()對(duì)于運(yùn)行一系列異步操作但只需要最快執(zhí)行操作的結(jié)果很有用。
方法Promise.any()_
Promise.any()接受 Promise 數(shù)組作為參數(shù),但返回單個(gè) Promise 作為輸出。它返回的單個(gè)承諾是輸入數(shù)組中第一個(gè)已解決的承諾。此方法等待數(shù)組中的任何承諾得到解決,并立即將其作為輸出返回。
?const promise1 = new Promise((resolve) =>
? ?setTimeout(resolve, 3000, `First Promise's Value`)
?);
?const promise2 = new Promise((resolve) =>
? ?setTimeout(resolve, 2000, `Second Promise's Value`)
?);
?const promise3 = Promise.reject(`Third Promise's Value`);
?
?Promise.any([promise1, promise2, promise3]);
?
?// Output on the console
?
?// *Promise {<fulfilled>: "Second Promise's Value"}*
從上面的例子來(lái)看,promise13秒后會(huì)解析,promise22秒后會(huì)解析,然后promise3立即拒絕。因?yàn)镻romise.any()正在尋找第一個(gè)成功的承諾,所以它返回promise2。promise1有點(diǎn)晚了,所以它落在后面了。
如果數(shù)組中的任何承諾都沒(méi)有得到解決,Promise.any()則返回一個(gè)被拒絕的承諾。這個(gè)被拒絕的承諾包含一個(gè) JavaScript 原因數(shù)組,其中每個(gè)原因都與輸入數(shù)組中的一個(gè)承諾相對(duì)應(yīng)。
?const promise1 = new Promise((resolve, reject) =>
? ?setTimeout(reject, 3000, `First rejection reason`)
?);
?const promise2 = new Promise((resolve, reject) =>
? ?setTimeout(reject, 2000, `Second rejection reason`)
?);
?const promise3 = Promise.reject(`Third rejection reason`);
?
?Promise.any([promise1, promise2, promise3]);
?
?// Output on the console
?
?// *Promise {<rejected>: Aggregate Error: All Promises were rejected}*
?
?Promise.any([promise1, promise2, promise3]).catch(({ errors }) =>
? ?console.log(errors)
?);
?
?// Output on the console
?
?// *(3) ["First* rejection reason*", "Second* rejection reason*", "Third* rejection reason*"]*
此方法對(duì)于異步操作很有用,其中最快的成功承諾就是您所需要的。Promise.any()并且Promise.race()是相似的,除了Promise.any()將返回最快完成和解決的承諾,而Promise.race()將返回最快完成的承諾并且不關(guān)心它是否已解決。
方法Promise.allSettled()_
Promise.allSettled()隨著ES2020的發(fā)布,成為了 JavaScript Promise 的一個(gè)特性。它像本文在本節(jié)中討論的其他 promise 方法一樣并行處理 promise。
Promise.allSettled()有助于編寫(xiě)更高效的異步代碼,因?yàn)樗@示數(shù)組中所有承諾的結(jié)果,無(wú)論狀態(tài)如何——已解決或已拒絕。
Promise.allSettled()接受一組承諾作為參數(shù),并返回一個(gè)承諾作為輸出。
它返回的單個(gè)承諾將始終解決或在所有輸入承諾都得到解決后進(jìn)入“已完成”狀態(tài)。它不關(guān)心輸入數(shù)組中是否有任何個(gè)別承諾被拒絕。數(shù)組Promise.all()resolve with 將包含輸入數(shù)組中承諾的解析值或拒絕原因。
?const promise1 = new Promise((resolve) =>
? ?setTimeout(resolve, 3000, `First Promise's Value`)
?);
?const promise2 = new Promise((resolve) =>
? ?setTimeout(resolve, 2000, `Second Promise's Value`)
?);
?const promise3 = Promise.reject(`Third Promise's Value`);
?
?Promise.allSettled([promise1, promise2, promise3]);
?
?// Output on the console
?
?// *Promise {<fulfilled>: Array(3)}*
?
?Promise.allSettled([promise1, promise2, promise3]).then(console.log);
?
?// Output on the console
?
?/*
?(3) [{…}, {…}, {…}]
?0: {status: 'fulfilled', value: "First Promise's Value"}
?1: {status: 'fulfilled', value: "Second Promise's Value"}
?2: {status: 'rejected', reason: "Third Promise's Value"}
?*/
從上面的示例中,您可以看到即使promise3在創(chuàng)建時(shí)拒絕,Promise.allSettled()仍然返回一個(gè)“已完成”的承諾。即使輸入數(shù)組中的所有承諾都被拒絕,它也會(huì)這樣做。
Promise.allSettled()類似于Promise.all()他們所有的輸入承諾必須在他們返回的承諾有一個(gè)穩(wěn)定的狀態(tài)之前解決——完成或拒絕。
不同的是,Promise.all()只有在輸入中的所有承諾都被解決時(shí)才能成功,而不Promise.allSettled()關(guān)心輸入承諾的狀態(tài)。
使用此方法可以讓您大致了解所有承諾的執(zhí)行情況、已解決的承諾和被拒絕的承諾。它提供有關(guān)您傳遞給它的所有承諾的完整信息,并允許您獨(dú)立檢查它們——一個(gè)結(jié)果不會(huì)影響該方法返回的承諾的狀態(tài)。
什么是異步/等待語(yǔ)法?
隨著ES8(ES2017)的發(fā)布,Async/await 語(yǔ)法成為 JavaScript 的一個(gè)特性。它建立在 promises 之上,您可以將其視為 promises 的替代語(yǔ)法。
async/await消除了 promises 語(yǔ)法中常見(jiàn)的鏈接,最終使異步代碼看起來(lái)更加同步。
Promises 是避免前面討論的“厄運(yùn)回調(diào)金字塔”的絕佳方式,但 async/await 使異步代碼更進(jìn)一步。使用 async/await,代碼更容易遵循和維護(hù)。它的出現(xiàn)是為了提高異步操作的代碼可讀性。這是使用承諾的現(xiàn)代方式。
如何在 JavaScript 中創(chuàng)建異步函數(shù)
?async`是用于創(chuàng)建函數(shù)的 JavaScript 關(guān)鍵字。this 關(guān)鍵字幫助創(chuàng)建的函數(shù)將始終返回一個(gè)承諾。要使用它,請(qǐng)?jiān)诼暶骱瘮?shù)時(shí)放在關(guān)鍵字`async`之前。`function
?async function example() {
? // Return a value
?}
?
?example()
?
?// Output on the console
?
?// *Promise {<fulfilled>: undefined}*
從代碼示例中,您可以看到該函數(shù)返回一個(gè)帶有 value 的 promise undefined。這是因?yàn)閍sync函數(shù)返回的任何內(nèi)容都將是結(jié)果承諾的解析值。在這種情況下,該函數(shù)不返回任何內(nèi)容,因此undefined.
?async function example() {
? ?return "Feels good to be an async function";
?}
?
?example();
?
?// Output on the console
?
?// *Promise {<fulfilled>: "Feels good to be an async function"}*
在上面的例子中,函數(shù)返回一個(gè)字符串,它成為結(jié)果承諾的解析值。這就是創(chuàng)建函數(shù)的方法async。
如何使用等待關(guān)鍵字
要使用await關(guān)鍵字,請(qǐng)將其放在承諾之前。async它是函數(shù)暫停執(zhí)行直到該承諾得到解決的指示器。
它類似于.then()確保承諾在繼續(xù)之前被“履行”或“拒絕”的方法。請(qǐng)注意,您只能在函數(shù)await內(nèi)部使用關(guān)鍵字async。
您可以重復(fù)await異步操作,而不是像.then()前面的文章所教導(dǎo)的那樣將 promise 與 鏈接起來(lái),從而使您的代碼更清晰、更易于閱讀。
?const timerPromise = (message) =>
? ?new Promise((resolve) => setTimeout(resolve, 3000, message));
?
?async function asyncFunc() {
? ?const result = await timerPromise("promise finished!");
? ?console.log(result);
?}
?
?// Output on the Console after 3 seconds
?
?// promise finished!
await在 promise 之前使用關(guān)鍵字將產(chǎn)生該 promise 的解析值。const result = await promise('promise finished!')從where ?result變成一個(gè)字符串而不是一個(gè)新的 promise 的行可以明顯看出。這不同于.then()which always returns a new promise。
使用await,您可以打破任何承諾鏈,并獲取它們的 resolve 值。以下示例使用該fetch()函數(shù)(它是一個(gè)承諾)來(lái)展示如何使用 async/await 消除鏈接。
?// With chaining
?fetch("jsonplaceholder.typicode.com/users")
? ?.then((response) => response.json())
? ?.then((result) => console.log(result));
?
?// Output on the console
?
?// Array(10) [...]
?
?// Without chaining
?async function fetchResource(url) {
? ?const response = await fetch(url);
? ?const result = await response.json();
? ?console.log(result);
?}
?fetchResource("jsonplaceholder.typicode.com/users");
?
?// Output on the console
?
?// Array(10) [...]
最后,它歸結(jié)為偏好和選擇。如果您更喜歡鏈接語(yǔ)法,那就去吧。如果你希望你的代碼看起來(lái)是同步的并且想使用 async/await,那也很好。
您還可以同時(shí)使用這兩種語(yǔ)法,將 promises 鏈接在一個(gè)異步函數(shù)中。這完全取決于您想要實(shí)現(xiàn)的目標(biāo)和您喜歡的風(fēng)格。
如何處理 Async/Await 中的錯(cuò)誤
就像使用普通的 promise 語(yǔ)法一樣,您可以使用 async/await 正確捕獲錯(cuò)誤。正確處理異步調(diào)用中的錯(cuò)誤對(duì)于跟蹤錯(cuò)誤極為重要。使用 try/catch 塊來(lái)執(zhí)行此操作。
try是包裝代碼塊的 JavaScript 關(guān)鍵字。當(dāng)該代碼塊運(yùn)行時(shí),try檢查錯(cuò)誤。沒(méi)有錯(cuò)誤可以逃脫 try 塊。try在函數(shù)內(nèi)部使用async。
塊內(nèi)的第一個(gè)錯(cuò)誤try會(huì)停止執(zhí)行該塊中的其他指令,try然后將錯(cuò)誤值傳遞給catch塊。該catch塊類似于.catch()承諾。就像 promise 方法一樣,它是一個(gè)錯(cuò)誤的函數(shù)。
?async function fetchResource(url) {
? ?try {
? ? ?const response = await fetch(url);
? ? ?const result = await response.json();
? ? ?console.log(result);
? ?} catch (error) {
? ? ?console.error(error);
? ?}
?}
在這個(gè)例子中,catch關(guān)鍵字有一個(gè)錯(cuò)誤,記錄到控制臺(tái)。帶有未捕獲錯(cuò)誤的 settled promise 會(huì)導(dǎo)致 rejected promise。確保將代碼包裝在 try/catch 塊中,以便更好地控制程序中的失敗和錯(cuò)誤。
此外,就像.finally()承諾的方法一樣,您可以finally在異步函數(shù)中使用塊。這個(gè)關(guān)鍵字后面的大括號(hào)環(huán)繞著一段代碼,無(wú)論是否有錯(cuò)誤都會(huì)運(yùn)行。
?async function fetchResource(url) {
? ?try {
? ? ?const response = await fetch(url);
? ? ?const result = await response.json();
? ? ?console.log(result);
? ?} catch (error) {
? ? ?console.error(error);
? ?} finally {
? ? ?console.log("Operation finished!");
? ?}
?}
塊的使用finally與方法的使用類似.finally()。這只是證明使用異步函數(shù)是最近使用 promises 的方法。
什么是作業(yè)隊(duì)列?
作業(yè)隊(duì)列——也稱為微任務(wù)隊(duì)列——是 JavaScript 中需要注意的一個(gè)重要概念。它最初不是 JavaScript 運(yùn)行時(shí)的組件,但是當(dāng) promises 成為 JavaScript 的一部分時(shí),對(duì)它的需求就來(lái)了。
考慮以下代碼示例:
?Promise.resolve("This is a resolved value").then(console.log);
?setTimeout(console.log, 0, "This is a value after the timeout");
?console.log("This is a normal log");
這里的第一行是一個(gè)自動(dòng)解析的承諾,然后在控制臺(tái)上記錄值。第二行是超時(shí)設(shè)置為 0 毫秒,這意味著它應(yīng)該是即時(shí)的。超時(shí)接受一個(gè)回調(diào)函數(shù),該函數(shù)將一個(gè)值記錄到控制臺(tái)。第三行是正常的控制臺(tái)日志。
當(dāng)您運(yùn)行該程序時(shí),您能猜出這些日志出現(xiàn)的順序嗎?讓我們找出來(lái)。
?// Output on the console
?/*
?This is a normal log
?This is a resolved value
?--
?undefined
?--
?This is a value after the timeout
?*/
這是一個(gè)有趣的輸出。第一條日志來(lái)自console.log. 它不是那么混亂,因?yàn)閏onsole.log()它不是異步操作。JavaScript 引擎會(huì)在程序啟動(dòng)后立即主動(dòng)運(yùn)行每條同步指令。
第二行可能有點(diǎn)令人費(fèi)解。它記錄了 promise 的解析值。為什么接下來(lái)是 promise 的輸出?好吧,簡(jiǎn)單的答案是 promise 比 JavaScript 中的任何其他異步實(shí)現(xiàn)都要快。但這還不是全部。
在 JavaScript 運(yùn)行時(shí),事件循環(huán)處理異步操作。它只能在調(diào)用棧為空時(shí)調(diào)用異步指令的回調(diào)函數(shù)。Resolving a promise 是一個(gè)異步操作,在普通日志之后是可以理解的。但是為什么它出現(xiàn)在setTimeout()指令之前呢?
JavaScript 運(yùn)行時(shí)實(shí)際上有這兩個(gè)隊(duì)列——回調(diào)(或宏任務(wù))隊(duì)列和作業(yè)(或微任務(wù))隊(duì)列。在事件循環(huán)開(kāi)始調(diào)用回調(diào)隊(duì)列中的函數(shù)之前不久,它會(huì)調(diào)用作業(yè)隊(duì)列中的所有指令。promise 的回調(diào)保留在作業(yè)隊(duì)列中,因此事件循環(huán)首先調(diào)用它。這就是為什么 promises 返回值比任何其他異步實(shí)現(xiàn)更快的原因。
除了 promises 之外,Job Queue 對(duì)于其他一些指令也很有用。但是,這超出了本材料的范圍。如果你很好奇,那么你可以在這里閱讀更多關(guān)于作業(yè)隊(duì)列的信息。
程序處理完作業(yè)隊(duì)列后立即返回。從上面的代碼示例中,它返回帶有undefined. 之后,事件循環(huán)移至回調(diào)隊(duì)列并執(zhí)行那里的指令。
圖片來(lái)源:Medium上的Saravanakumar
setTimeout()無(wú)論計(jì)時(shí)器有多短,回調(diào)都會(huì)一直移動(dòng)到回調(diào)隊(duì)列。在示例中,即使計(jì)時(shí)器設(shè)置為 0 毫秒,它也會(huì)最后記錄。
至此,我希望您能理解為什么示例代碼會(huì)產(chǎn)生該輸出,以及回調(diào)隊(duì)列和微任務(wù)隊(duì)列之間的區(qū)別。
結(jié)論
這是對(duì)承諾和異步操作的深入研究。在本文中,您了解了 JavaScript 中的承諾是如何產(chǎn)生的、它們是什么以及如何創(chuàng)建它們。 ?
您還學(xué)習(xí)了如何將回調(diào)附加到承諾、如何捕獲承諾中的錯(cuò)誤以及如何同時(shí)運(yùn)行多個(gè)承諾。
我們還研究了構(gòu)建在 promises 之上的 async/await 語(yǔ)法。您了解了它們何時(shí)成為 JavaScript 的一項(xiàng)功能、如何創(chuàng)建異步函數(shù)以及如何使用 await 關(guān)鍵字。
您還學(xué)習(xí)了如何使用語(yǔ)法處理錯(cuò)誤。最后,文章解釋了什么是 Job Queue。
如果您沒(méi)有一下子理解所有內(nèi)容,請(qǐng)隨時(shí)回來(lái)。學(xué)習(xí)和掌握 JavaScript promises 可能需要時(shí)間,但任何 JavaScript 開(kāi)發(fā)人員都將從了解 promises 的工作原理中獲益匪淺。在為您的應(yīng)用程序編寫(xiě)專業(yè)的異步代碼時(shí),它讓您有更多的控制權(quán)。
祝你構(gòu)建下一個(gè)項(xiàng)目好運(yùn)。