您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“JavaScript中使用Promises的三個錯誤是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
第一個錯誤也是最為明顯的錯誤之一,但是我發(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)用一個方法會更改一個對象,而非創(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)用。
兩段代碼的第二個不同之處是,在第二段代碼中 doFirstThingWithResult
和 doSecondThingWithResult
都會接收到同樣的參數(shù) —— somePromise
成功執(zhí)行返回的結果,兩個回調(diào)函數(shù)的返回值在這個示例中被完全忽略掉了。
這個誤區(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ì)量的實用文章!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。