溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

JavaScript中使用Promises的三個錯誤是什么

發(fā)布時間:2022-01-26 15:35:19 來源:億速云 閱讀:104 作者:iii 欄目:開發(fā)技術

本篇內(nèi)容介紹了“JavaScript中使用Promises的三個錯誤是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

將所有內(nèi)容包裝在 Promise 構造函數(shù)中

第一個錯誤也是最為明顯的錯誤之一,但是我發(fā)現(xiàn)開發(fā)者犯這個錯誤的頻率出奇的高。

當?shù)谝淮螌W習 Promises 時,你會了解到 Promise 的構造函數(shù),這個構造函數(shù)可以用來創(chuàng)建一個新的 Promises 對象。

也許因為人們通常是通過將一些瀏覽器 API(例如 setTimeout)包裝在 Promise 構造函數(shù)中這種方式來開始學習的,所以在他們心中根深蒂固地認為創(chuàng)建 Promise 對象的唯一方法是使用構造函數(shù)。

因此,通常會這樣寫:

const createdPromise = new Promise(resolve => {
  somePreviousPromise.then(result => {
    // 對 result 進行一些操作
    resolve(result);
  });
});

可以看到,為了對 somePreviousPromise 的結果 result 進行一些操作,有些人使用了 then,但是后來決定將其再次包裝在一個 Promise 的構造函數(shù)中,為的是將該操作的結果存儲在 createdPromise 的變量中,大概是為了稍后對該 Promise 進行更多操作。

這顯然是沒有必要的。then 方法的全部要點在于它本身會返回一個 Promise,它表示的是執(zhí)行 somePreviousPromise 后再執(zhí)行then 中的的回調(diào)函數(shù),then 的參數(shù)是 somePreviousPromise成功執(zhí)行返回的結果。

所以,上一段代碼大致等價于:

const createPromise = somePreviousPromise.then(result => {
  // 對 result 進行一些操作
  return result
})

如此編寫,會簡潔很多。

但是,為什么我說它只是大致等價呢?區(qū)別在哪里?

經(jīng)驗不足且不細心觀察的話可能很難發(fā)現(xiàn),實際上兩者在錯誤處理上存在巨大的差異,這種差異比第一段代碼的冗余問題更為重要。

假設 somePreviousPromise 出于某些原因失敗了且拋出錯誤。例如,這個 Promise 里發(fā)送了一個 HTTP 請求,而 API 響應 500 錯誤。

事實證明,在上一段代碼中,我們將一個 Promise 包裝到另一個 Promise 中,我們根本無法捕獲該錯誤。為了解決此問題,我們必須進行以下更改:

const createdPromise = new Promise((resolve, reject) => {
  somePreviousPromise.then(result => {
    // 對 result 進行一些操作
    resolve(result);
  }, reject);
});

我們簡單的在回調(diào)函數(shù)中添加了一個 reject 參數(shù),然后通過將其作為第二個參數(shù)傳遞給 then 的方式來使用它。請務必記住,then 方法接受第二個可選參數(shù)來進行錯誤處理,這一點非常重要。

現(xiàn)在如果 somePreviousPromise 出于某些原因失敗了,reject函數(shù)將會被調(diào)用,并且我們將能夠一如往常地處理 createdPromise上的錯誤。

這樣是否解決了所有問題?抱歉,并沒有。

我們處理了 somePreviousPromise 自身可能發(fā)生的錯誤,但是我們?nèi)匀粺o法控制作為 then 方法第一個參數(shù)的回調(diào)函數(shù)中發(fā)生的情況。在注釋區(qū)域 // 對 result 進行一些操作 執(zhí)行的代碼可能會有一些錯誤,如果這塊地方的代碼拋出任何錯誤,那 then 方法的第二個參數(shù)reject 依舊捕獲不到這些錯誤。

這是因為作為 then 方法的第二個參數(shù)的錯誤處理函數(shù)只對 Promise 鏈上當前 then 之前發(fā)生的錯誤作出響應。

因此,最合適的(也是最終的)解決方案應該如下:

const createdPromise = new Promise((resolve, reject) => {
  somePreviousPromise.then(result => {
    // 對 result 進行一些操作
    resolve(result);
  }).catch(reject);
});

注意,這次我們使用了 catch 方法 —— 因為它將在第一個 then之后被調(diào)用,它將捕獲到 Promise 鏈上拋出的所有錯誤。無論是 somePreviousPromise 還是 then 中的回調(diào)失敗了,Promise 都將按預期處理這些情況。

從上述示例可以發(fā)現(xiàn),在 Promise 的構造函數(shù)中包裝代碼時,有很多細節(jié)問題需要處理。這就是為什么最好使用 then 方法創(chuàng)建新的 Promises 的原因,如第二段代碼所示。它不僅看起來優(yōu)雅,并且還可以幫助我們避免那些極端情況。

串行調(diào)用 then 與并行調(diào)用 then 的比較

由于許多程序員都有著面向對象的編程背景,因此對他們來說,調(diào)用一個方法會更改一個對象,而非創(chuàng)建一個新的對象,這很稀松平常。

這或許也是我看到有人對于「在 Promise 上調(diào)用 then 方法時」到底發(fā)生了什么會感到困惑的原因。

比較下面兩段代碼:

const somePromise = createSomePromise();


somePromise
  .then(doFirstThingWithResult)
  .then(doSecondThingWithResult);
const somePromise = createSomePromise();


somePromise
  .then(doFirstThingWithResult);


somePromise
  .then(doSecondThingWithResult);

它們所做之事是否相同?看起來似乎相同,畢竟,兩段代碼都在 somePromise 上調(diào)用了兩次 then,對嗎?

不,這又是一個非常普遍的誤區(qū)。實際上,這兩段代碼做的事情完全不同。如果不完全理解兩段代碼中正在做的事情,可能會導致出現(xiàn)非常棘手的錯誤。

正如我們在之前的章節(jié)中所說,then 方法會創(chuàng)建一個完全新的、獨立的 Promise。這意味著在第一段代碼中,第二個 then 方法不是在 somePromise 上調(diào)用,而是在一個新的 Promise 對象上調(diào)用,這段代碼表示等待 somePromise 的狀態(tài)變?yōu)槌晒罅⒖陶{(diào)用 doFirstThingWithResult。然后給新返回的 Promise 實例添加一個回調(diào)操作 doSecondThingWithResult

實際上,這兩個回調(diào)將會一個接著一個地執(zhí)行 —— 可以確保只有在第一個回調(diào)執(zhí)行完成且沒有任何問題之后,才會調(diào)用第二個回調(diào)。此外,第一個回調(diào)將會接收 somePromise 返回的值作為參數(shù),但是第二個回調(diào)函數(shù)將接收 doFirstThingWithResult 函數(shù)返回的值作為參數(shù)。

另一方面,在第二段代碼中,我們在 somePromise 上調(diào)用兩次then 方法,基本上忽略了從該方法返回的兩個新的 Promises 對象。因為 then 在完全相同的 Promise 實例上被調(diào)用了兩次,因此我們無法確定首先執(zhí)行哪個回調(diào),這里的執(zhí)行順序是不確定的。

從某種意義上說,這兩個回調(diào)應該是獨立的,并且不依賴于任何先前調(diào)用的回調(diào),我有時將其視為 “并行” 的執(zhí)行。但是,當然,實際上,JS 引擎同一時刻只能執(zhí)行一個功能 —— 你根本無法知道它們將以什么順序調(diào)用。

兩段代碼的第二個不同之處是,在第二段代碼中 doFirstThingWithResultdoSecondThingWithResult 都會接收到同樣的參數(shù) —— somePromise 成功執(zhí)行返回的結果,兩個回調(diào)函數(shù)的返回值在這個示例中被完全忽略掉了。

創(chuàng)建后立即執(zhí)行 Promise

這個誤區(qū)出現(xiàn)的原因也是因為大部分程序員有著豐富的面向對象編程經(jīng)驗。

在面向對象編程的思想中,確保對象的構造函數(shù)自身不執(zhí)行任何操作通常被認為是一種很好的實踐。舉個例子,一個代表數(shù)據(jù)庫的對象在使用new 關鍵字調(diào)用其構造函數(shù)時不應該啟動與數(shù)據(jù)庫的鏈接。

相反,應該提供一個特定的方法,如調(diào)用一個名為 init 的方法 —— 它將顯式地創(chuàng)建連接。這樣,一個對象不會因為已被創(chuàng)建而執(zhí)行任何期望之外的操作。它會按照程序員的明確要求來執(zhí)行。

但這「不是 Promises 的工作方式」。

考慮如下示例:

const somePromise = new Promise(resolve => {
  // 創(chuàng)建 HTTP 請求
  resolve(result);
});

你可能會認為發(fā)出 HTTP 請求的函數(shù)未在此處調(diào)用,因為它包裝在 Promise 構造函數(shù)中。實際上,許多程序員希望 somePromise 上執(zhí)行 then 方法之后它才被調(diào)用。

但事實并非如此。創(chuàng)建該 Promise 后,回調(diào)將立即執(zhí)行。這意味著當您在創(chuàng)建 somePromise 變量后進入下一行時,你的 HTTP 請求可能已被執(zhí)行,或者說已存在執(zhí)行隊列里。

我們說 Promise 是 “eager” 的,因為它盡可能快地執(zhí)行與其關聯(lián)的動作。相反,許多人期望 Promises 是 “l(fā)azy” 的,即僅在必要時調(diào)用(例如,當 then 方法在 Promise 上首次被調(diào)用)。這是一個誤區(qū),Promise 永遠是 eager 的,而非 lazy 的。

但是,如果您想要延遲執(zhí)行 Promise,應該怎么做?如果您希望延遲發(fā)出該 HTTP 請求怎么辦?Promises 中是否內(nèi)置了某種奇特的機制,可以讓您執(zhí)行類似的操作?

答案有時會超出開發(fā)者們的期望。函數(shù)是一種 lazy 機制。僅當程序員使用 () 語法顯式調(diào)用它們時,才執(zhí)行它們。僅僅定義一個函數(shù)實際上并不能做任何事情。因此,要使 Promise 成為 “l(fā)azy”, 最佳方法是將其簡單地包裝在函數(shù)中!

具體代碼如下:

const createSomePromise = () => new Promise(resolve => {
  // 創(chuàng)建 HTTP 請求
  resolve(result);
});

現(xiàn)在,我們將 Promise 構造函數(shù)的調(diào)用操作包裝在一個函數(shù)中。事實上它還沒有真正被調(diào)用。我們還將變量名從 somePromise 更改為 createSomePromise,因為它不再是一個 Promise 對象 —— 而是一個創(chuàng)建并返回 Promise 對象的函數(shù)。

Promise 構造函數(shù)(以及帶有 HTTP 請求的回調(diào)函數(shù))僅在執(zhí)行該函數(shù)時被調(diào)用。因此,現(xiàn)在我們有了一個 lazy 的 Promise,只有在我們真正想要它執(zhí)行時才去執(zhí)行它。

此外,請注意,它還附帶提供了另一種功能。我們可以輕松地創(chuàng)建另一個可以執(zhí)行相同操作的 Promise 對象。

如果出于某些奇怪的原因,我們希望進行兩次相同的 HTTP 請求并同時執(zhí)行這些請求,則只需要兩次調(diào)用 createSomePromise 函數(shù)。又或者,如果請求由于任何原因失敗了,我們可以使用相同的函數(shù)重新請求。

這表明將 Promises 包裝在函數(shù)(或方法)中非常方便,因此對于 JavaScript 開發(fā)人員來說,使用這種模式開發(fā)應該要變得很自然而然。

而諷刺的是,如果你閱讀過我寫的文章 Promises vs Observables ,你就會知道編寫 Rx.js 的程序員經(jīng)常會犯一個與此相反的錯誤。他們對 Observable 進行編碼,就好像它們是 “eager”(與 Promises 一致),而實際上它們是 ”lazy“ 的。因此,將 Observables 封裝在函數(shù)或方法中通常沒有任何意義,實際上甚至是有害的。

“JavaScript中使用Promises的三個錯誤是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關的知識可以關注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI