溫馨提示×

溫馨提示×

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

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

Promise的概念和作用是什么

發(fā)布時間:2021-07-20 17:38:04 來源:億速云 閱讀:151 作者:chen 欄目:web開發(fā)

這篇文章主要講解了“Promise的概念和作用是什么”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Promise的概念和作用是什么”吧!

 從異步編程說起

我們都知道 JavaScript  的代碼執(zhí)行的時候是跑在單線程上的,可以理解為只能按照代碼的出現(xiàn)順序,從上到下一行一行的執(zhí)行,但是遇到了異步的行為,比如定時器(一定時間之后才去執(zhí)行),那就需要等同步代碼執(zhí)行完成后的一段時間里再去執(zhí)行異步代碼。

對于同步行為,如下面的代碼,我們能夠很清楚的知道每一行會發(fā)生什么,這是因為后面的指令總是等到前面的指令執(zhí)行完成后才去執(zhí)行,所以這里的第二行里的變量 x  在內(nèi)存里已經(jīng)是定義過的。

let x = 10;let y = x + 5;

但是對于異步代碼,我們就不好推斷到底什么時候會執(zhí)行完成了。比如舉一個實際的例子,我們?nèi)討B(tài)加載某個腳本,會這樣做:

function loadScript(src) {     let script = document.createElement('script')     script.src = src     document.head.append(script) }

這個腳本加載完成的時候會去執(zhí)行定義在腳本里的一些函數(shù),比如初始化函數(shù) init,那么我們可以會這樣寫:

function loadScript(src) {     let script = document.createElement('script')     script.src = src     document.head.append(script) } loadScript('./js/script.js') init()  // 定義在 ./js/script.js 里的函數(shù)

但是實際執(zhí)行后卻發(fā)現(xiàn),這樣根本不行,因為加載腳本是需要花時間的,是一個異步的行為,瀏覽器執(zhí)行 JavaScript  的時候并不會等到腳本加載完成的時候再去調(diào)用 init 函數(shù)。

以往,對于這種異步編程的做法通常就是通過給函數(shù)傳遞一個回調(diào)函數(shù)來處理,上面那個例子可以這樣做:

function loadScript(src, success, fail) {     let script = document.createElement('script')     script.src = src     script.onload = success     script.onerror = fail     document.head.append(script) } loadScript('./js/script.js', success, fail) function success() {     console.log('success')     init()  // 定義在 ./js/script.js 中的函數(shù) } function fail() {     console.log('fail') }

上面這樣做能夠保證在腳本加載完成的時候,再去執(zhí)行腳本里的函數(shù)。但是多考慮一個問題,如果 success 里又需要加載別的 js  文件呢,那豈不是需要多層嵌套了。是的,這樣的多層嵌套會使得代碼層次變得更加深入,難以閱讀以及后期維護(hù)成本非常高,尤其是當(dāng)里面加上了很多的判斷邏輯的時候情況會更加糟糕,這就是所謂的  “回調(diào)地獄”,且又因為它的代碼形狀很像躺著的金字塔,所以有的人也喜歡叫它 “噩運(yùn)金字塔”。

而為了避免這類 “回調(diào)地獄” 問題,目前最好的做法之一就是使用 Promise。

Promise正篇

使用 Promise 可以很好的解決上面提到的 “回調(diào)地獄” 問題,直接來看結(jié)果:

function loadScript(src) {     return new Promise(function(resolve, reject) {         let script = document.createElement('script');         script.src = src;         script.onload = () => resolve(script);         script.onerror = () => reject(new Error(`Script load error for ${src}`));         document.head.append(script);     }); } loadScript('./scripts.js').then(res => {     console.log('success', res);     init() }).catch(err => {     console.log(err); })

這里通過使用 Promise 實例的 then 和 catch 函數(shù)將多層嵌套的代碼改成了同步處理流程,看起來效果還是不錯的,那什么是 Promise  呢?

  • Promise  首先是一個對象,它通常用于描述現(xiàn)在開始執(zhí)行,一段時間后才能獲得結(jié)果的行為(異步行為),內(nèi)部保存了該異步行為的結(jié)果。然后,它還是一個有狀態(tài)的對象:

  • pending:待定

  • fulfilled:兌現(xiàn),有時候也叫解決(resolved)

  • rejected:拒絕

  • 一個 Promise 只有這 3 種狀態(tài),且狀態(tài)的轉(zhuǎn)換過程有且僅有 2 種:

  • pending 到 fulfilled

  • pending 到 rejected

可以通過如下的 Promise 對象構(gòu)造器來創(chuàng)建一個 Promise:

let promise = new Promise((resolve, reject) => {})

傳遞給 new Promise 的是 executor 執(zhí)行器。當(dāng) Promise 被創(chuàng)建的時候,executor 會立即同步執(zhí)行。executor  函數(shù)里通常做了 2 件事情:初始化一個異步行為和控制狀態(tài)的最終轉(zhuǎn)換。

new Promise((resolve, reject) => {     setTimeout(() => {         resolve()     }, 1000) })

如上代碼所示,setTimeout 函數(shù)用來描述一個異步行為,而 resolve 用來改變狀態(tài)。executor 函數(shù)包含 2  個參數(shù),他們都是回調(diào)函數(shù),用于控制 Promise 的狀態(tài)轉(zhuǎn)換:

  • resolve:用來將狀態(tài) pending 轉(zhuǎn)換成 fulfilled

  • reject:用來將狀態(tài) pending 轉(zhuǎn)換成 rejected

一個 Promise 的狀態(tài)一旦被轉(zhuǎn)換過,則無法再變更:

let p = new Promise((resolve, reject) => {     setTimeout(() => {         resolve('第一次 resolve')         resolve('第二次 resolve')  // 將被忽略         reject('第一次 reject')  // 將被忽略     }, 0) }) setTimeout(console.log, 1000, p)  // Promise {<fulfilled>: "第一次 resolve"}

可以看到執(zhí)行了 2 次 resolve 函數(shù)和 1 次 reject 函數(shù),但是 promise 的最終結(jié)果是取的第一次 resolve  的結(jié)果,印證了上面的結(jié)論。

由 new Promise 構(gòu)造器返回的 Promise 對象具有如下內(nèi)部屬性:

  • PromiseState:最初是 pending,resolve 被調(diào)用的時候變?yōu)?fulfilled,或者 reject 被調(diào)用時會變?yōu)? rejected;

  • PromiseResult:最初是 undefined,resolve(value) 被調(diào)用時變?yōu)?value,或者在 reject(error)  被調(diào)用時變?yōu)?error。

比如上面例子中打印出來的 Promise 對象結(jié)果中,fulfilled 是其內(nèi)部的 PromiseState,而 “第一次 resolve” 是其  PromiseResult。

// Promise {<fulfilled>: "第一次 resolve"}

Promise實例方法

Promise.prototype.then()

Promise.prototype.then() 將用于為 Promise 實例添加處理程序的函數(shù)。它接受 2 個可選的參數(shù):

  • onResolved:狀態(tài)由 pending 轉(zhuǎn)換成 fulfilled 時執(zhí)行;

  • onRejected:狀態(tài)由 pending 轉(zhuǎn)換成 rejected 時執(zhí)行。

它可以寫成這樣:

function onResolved(res) {     console.log('resolved' + res)  // resolved3 } function onRejected(err) {     console.log('rejected' + err) } new Promise((resolve, reject) => {     resolve(3) }).then(onResolved, onRejected)

或者寫成更簡單的方式:

new Promise((resolve, reject) => {     resolve(3) }).then(res => {     console.log('resolved' + res)  // resolved3 }, err => {     console.log('rejected' + err) })

因為狀態(tài)的變化只有 2 種,所以 onResolved 和 onRejected 在執(zhí)行的時候必定是互斥。

上面介紹到了 then() 的參數(shù)是可選的,當(dāng)只有 onResolved 的時候可以這樣寫:

new Promise((resolve, reject) => {     resolve() }).then(res => {})

當(dāng)參數(shù)只有 onRejected 的時候,需要把第一個參數(shù)設(shè)置為 null:

new Promise((resolve, reject) => {     reject() }).then(null, err => {})

如果給 then() 函數(shù)傳遞來了非函數(shù)參數(shù),則會默認(rèn)忽略。

Promise.prototype.catch()

Promise.prototype.catch() 用于給 Promise 對象添加拒絕處理程序。只接受一個參數(shù):onRejected  函數(shù)。實際上,下面這兩種寫法是等效的:

function onRejected(err){} new Promise((resolve, reject) => {     reject() }).catch(onRejected) new Promise((resolve, reject) => {     reject() }).then(null, onRejected)

Promise.prototype.finally()

Promise.prototype.finally() 用于給 Promise 對象添加 onFinally  函數(shù),這個函數(shù)主要是做一些清理的工作,只有狀態(tài)變化的時候才會執(zhí)行該 onFinally 函數(shù)。

function onFinally() {     console.log(888)  // 并不會執(zhí)行   } new Promise((resolve, reject) => {      }).finally(onFinally)

因為 onFinally 函數(shù)是沒有任何參數(shù)的,所以在其內(nèi)部其實并不知道該 Promise 的狀態(tài)是怎么樣的。

鏈?zhǔn)秸{(diào)用

鏈?zhǔn)秸{(diào)用里涉及到的知識點很多,我們不妨先看看下面這道題,你能正確輸出其打印順序嘛?

new Promise((resolve, reject) => {     resolve() }).then(() => {     console.log('A')     new Promise((resolve, reject) => {         resolve()     }).then(() => {         console.log('B')     }).then(() => {         console.log('C')     }) }).then(() => {     console.log('D') })

這里我不給出答案,希望你能動手敲一敲代碼,然后思考下為什么?容我講完這部分知識,相信你能自己理解其中緣由。

從上面這串代碼里,我們看到 new Promise 后面接了很多的 .then() 處理程序,這個其實就是 Promise  的鏈?zhǔn)秸{(diào)用,那它為什么能鏈?zhǔn)秸{(diào)用呢?

基于onResolved生成一個新的Promise

因為 Promise.prototype.then() 會返回一個新的 Promise,來看下:

let p1 = new Promise((resolve, reject) => {     resolve(3) }) let p2 = p1.then(() => 6) setTimeout(console.log, 0, p1)  // Promise {<fulfilled>: 3} setTimeout(console.log, 0, p2)  // Promise {<fulfilled>: 6}

可以看到 p1 和 p2 的內(nèi)部 PromiseResult 是不一樣的,說明 p2 是一個新的 Promise 實例。

新產(chǎn)生的 Promise 會基于 onResolved 的返回值進(jìn)行構(gòu)建,構(gòu)建的時候其實是把返回值傳遞給 Promise.resolve()  生成的新實例,比如上面那串代碼里 p1.then(() => 6) 這里的 onResolved 函數(shù)返回了一個 6 ,所以新的 Promise  的內(nèi)部值會是 6。

如果 .then() 沒有提供 onResolved 這個處理程序,則 Promise.resolve() 會基于上一個實例 resolve  后的值來初始化一個新的實例:

let p1 = new Promise((resolve, reject) => {     resolve(3) }) let p2 = p1.then() setTimeout(console.log, 0, p2)  // Promise {<fulfilled>: 3}

如果 onResolved 處理程序沒有返回值,那么返回的新實例的內(nèi)部值會是 undefined:

let p1 = new Promise((resolve, reject) => {     resolve(3) }) let p2 = p1.then(() => {}) setTimeout(console.log, 0, p2)  // Promise {<fulfilled>: undefined}

如果在 onResolved 處理程序里拋出異常,則會返回一個新的 rejected 狀態(tài)的 Promise:

let p1 = new Promise((resolve, reject) => {     resolve(3) }) let p2 = p1.then(() => {     throw new Error('這是一個錯誤')} ) setTimeout(console.log, 0, p2)  // Promise {<rejected>: 這是一個錯誤}

基于onRejected生成一個新的Promise

基于 onRejected 的返回值也會返回一個新的 Promise,而且處理邏輯也是一樣的,也是通過把返回值傳遞給 Promise.resolve()  產(chǎn)生一個新的實例:

let p1 = new Promise((resolve, reject) => {     reject(3) })  // 沒有 `onRejected` 處理程序時,會原樣向后傳,不過是新實例 let p2 = p1.then(() => {})  s setTimeout(console.log, 0, p2)  // Promise {<rejected>: 3}  // 返回值為undefined時 let p3 = p1.then(null, () => {})  setTimeout(console.log, 0, p3)  // Promise {<fulfilled>: undefined}   // 返回值有實際值的時候 let p4 = p1.then(null, () => 6)  setTimeout(console.log, 0, p4)  // Promise {<fulfilled>: 6}  // 當(dāng)返回值是Promise時,會保留當(dāng)前Promise let p5 = p1.then(null, () => Promise.reject())  setTimeout(console.log, 0, p5)  // Promise {<rejected>: undefined}   // 當(dāng)遇到一個錯誤的時候 let p6 = p1.then(null, () => {     throw new Error('error') })  setTimeout(console.log, 0, p6)  // Promise {<rejected>: error}   // 當(dāng)返回值是一個錯誤時 let p7 = p1.then(null, () => new Error('error'))  setTimeout(console.log, 0, p7)  // Promise {<fulfilled>: Error: error}

這里你會不會有個疑惑?實例 resolve() 的時候,狀態(tài)由 pending 變成 rejected,從而調(diào)用 onRejected  進(jìn)行處理,但是為什么有時候會返回一個 fulfilled 的新實例呢?試著想一下,如果 onRejected 返回了一個 pending 的或者  rejected 狀態(tài)的新實例,那后續(xù)的鏈?zhǔn)秸{(diào)用就進(jìn)行不下去了,看下面例子:

new Promise((resolve, reject) => {     reject() }).then(null, () => {     console.log('A') }).then(() => {     console.log('B') }).then(() => {     console.log('C') }).catch(() => {     console.log('D') })

如果 A 處理函數(shù)這里返回了一個 pending 狀態(tài)的新實例,那么后續(xù)所有的鏈?zhǔn)讲僮鞫紵o法執(zhí)行;或者返回的是一個 rejected  狀態(tài)的新實例,那么后續(xù)的 B 和 C 也就無法執(zhí)行了,那居然都不能執(zhí)行 B 和 C  所在處理程序,那定義來干嘛呢?鏈?zhǔn)讲僮骶秃翢o鏈?zhǔn)娇裳?。又,onRejected 的存在的根本意義無非就是用于捕獲 Promise  產(chǎn)生的錯誤,從而不影響程序的正常執(zhí)行,所以默認(rèn)情況下理應(yīng)返回一個 fulfilled 的新實例。

Promise.prototype.catch() 也會生成一個新的 Promise,其生成規(guī)則和 onRejected 是一樣的。

finally生成一個新的Promise

沒想到吧,Promise.prototype.finally() 也能生成一個 Promise。finally  里的操作是和狀態(tài)無關(guān)的,一般用來做后續(xù)代碼的處理工作,所以 finally 一般會原樣后傳父 Promise,無論父級實例是什么狀態(tài)。

let p1 = new Promise(() => {}) let p2 = p1.finally(() => {}) setTimeout(console.log, 0, p2)  // Promise {<pending>}  let p3 = new Promise((resolve, reject) => {     resolve(3) }) let p4 = p3.finally(() => {}) setTimeout(console.log, 0, p3)  // Promise {<fulfilled>: 3}

上面說的是一般,但是也有特殊情況,比如 finally 里返回了一個非 fulfilled 的 Promise  或者拋出了異常的時候,則會返回對應(yīng)狀態(tài)的新實例:

let p1 = new Promise((resolve, reject) => {     resolve(3) }) let p2 = p1.finally(() => new Promise(() => {})) setTimeout(console.log, 0, p2)  // Promise {<pending>}  let p3 = p1.finally(() => Promise.reject(6)) setTimeout(console.log, 0, p3)  // Promise {<rejected>: 6}  let p4 = p1.finally(() => {     throw new Error('error') }) setTimeout(console.log, 0, p4)  // Promise {<rejected>: Error: error}

執(zhí)行順序

先來看一段簡單的代碼:

new Promise((resolve, reject) => {     console.log('A')     resolve(3)     console.log('B') }).then(res => {     console.log('C') }) console.log('D') // 打印結(jié)果:A B D C

上面這串代碼的輸出順序是:A B D C。從上面章節(jié)介紹的知識點我們知道,executor 執(zhí)行器會在 new Promise  調(diào)用的時候立即同步執(zhí)行的,所以先后打印 A B 是沒問題的。當(dāng)執(zhí)行 resolve()/reject() 的時候,會將 Promise  對應(yīng)的處理程序推入微任務(wù)隊列,稍等這里提到的對應(yīng)的處理程序具體是指什么?

  • resolve() 對應(yīng) .then() 里的第一個入?yún)ⅲ?onResolved 函數(shù);

  • reject() 對應(yīng) .then() 里的第二個入?yún)?,?onRejected 函數(shù);或者 Promise.prototype.catch()  里的回調(diào)函數(shù);

所以當(dāng)執(zhí)行 resolve(3) 的時候(此時下面定義的這個箭頭函數(shù)其實就是 onResolved 函數(shù)),onResolved  函數(shù)將被推入微任務(wù)隊列,然后打印 D,此時所有同步任務(wù)執(zhí)行完成,瀏覽器會去檢查微任務(wù)隊列,發(fā)現(xiàn)存在一個,所以最后會去調(diào)用 onResolved 函數(shù),打印出  C。

let onResolved = res => {     console.log('C') }

其實除了 onResolved、onRejected 以及 Promise.prototype.catch()  里的處理程序外,Promise.prototype.finally() 的處理程序 onFinally 也是異步執(zhí)行的:

new Promise((resolve, reject) => {     console.log('A')     resolve(3) }).finally(() => {     console.log('B') }) console.log('C') // 打印結(jié)果:A C B

Promise 鏈?zhǔn)秸{(diào)用的基礎(chǔ)就是因為 onResolved、onRejected、catch() 的處理程序以及 onFinally 會產(chǎn)生一個新的  Promise 實例,且又因為他們都是異步執(zhí)行的,所以在鏈?zhǔn)秸{(diào)用的時候,對于它們執(zhí)行順序會稀里糊涂琢磨不透就是這個原因。

題目一

那下面我們就來看點復(fù)雜的例子,先來分析下這章開篇提到的題目:

new Promise((resolve, reject) => {     resolve() }).then(() => {     console.log('A')     new Promise((resolve, reject) => {         resolve()     }).then(() => {         console.log('B')     }).then(() => {         console.log('C')     }) }).then(() => {     console.log('D') }) // 打印結(jié)果:A

為了方便分析,我們把上面的這串代碼寫得好看一點:

new Promise(executor).then(onResolvedA).then(onResolvedD)  function executor(resolve, reject) {     resolve() } function onResolvedA() {     console.log('A')     new Promise(executor).then(onResolvedB).then(onResolvedC) } function onResolvedB() {     console.log('B') } function onResolvedC() {     console.log('C') } function onResolvedD() {     console.log('D') }

執(zhí)行過程:

  • 執(zhí)行 new Promise(),立即同步執(zhí)行 executor 函數(shù),調(diào)用 resolve(),此時會將 onResolvedA 推入微任務(wù)隊列  1,截止目前所有同步代碼執(zhí)行完成;

  • 檢查微任務(wù)隊列,執(zhí)行 onResolvedA 函數(shù),打印 A,執(zhí)行 new Promise(executor),調(diào)用 resolve() 函數(shù),此時將  onResolvedB 推入微任務(wù)隊列 2;

  • 截止目前微任務(wù)隊列 1 的代碼全部執(zhí)行完成,即 onResolvedA 函數(shù)執(zhí)行完成。我們知道 onResolved 函數(shù)會基于返回值生成一個新的  Promise,而 onResolvedA 函數(shù)沒有顯示的返回值,所以其返回值為 undefined,那么經(jīng)過  Promise.resolve(undefined) 初始化后會生成一個這樣的新實例:Promise {

    :  undefined};由于這個新的實例狀態(tài)已經(jīng)變成 fulfilled,所以會立即將其處理函數(shù) onResolvedD 推入微任務(wù)隊列 3;
  • 開始執(zhí)行微任務(wù)隊列 2 里的內(nèi)容,打印 B,同上一條原理,由于 onResolvedB 函數(shù)的返回值為 undefined,所以生成了一個  resolved 的新實例,則會立即將 onResolvedC 推入微任務(wù)隊列 4;

  • 執(zhí)行微任務(wù)隊列 3,打印 D;

  • 執(zhí)行微任務(wù)隊列 4,打印 C;

  • 至此全部代碼執(zhí)行完成,最終的打印結(jié)果為:A B D C。

題目二

new Promise((resolve, reject) => {     resolve(1) }).then(res => {     console.log('A') }).finally(() => {     console.log('B') }) new Promise((resolve, reject) => {     resolve(2) }).then(res => {     console.log('C') }).finally(() => {     console.log('D') }) // 打印結(jié)果:A C B D

應(yīng)該很多人會和我當(dāng)初一樣好奇:為什么打印結(jié)果不是 A B C D 呢?這里涉及到一個知識點:如果給 Promise  實例添加了多個處理函數(shù),當(dāng)實例狀態(tài)變化的時候,那么執(zhí)行的過程就是按照添加時的順序而執(zhí)行的。

new Promise((resolve, reject) => {     resolve(1) }).then(onResolvedA).finally(onFinally)  function onResolvedA() {     console.log('A') } function onFinally() {     console.log('B') } // 打印結(jié)果:A B

對于上面這串代碼,其實 finally() 處理程序執(zhí)行的時候已經(jīng)不是通過 new Promise() 初始化的實例,而是執(zhí)行完 onResolvedA  函數(shù)的時候生成的新實例,不信我們將上面代碼中的函數(shù) onResolvedA 稍微改動下:

new Promise((resolve, reject) => {     resolve(1) }).then(onResolvedA).finally(onFinally)  function onResolvedA() {     console.log('A')     return new Promise(() => {}) } function onFinally() {     console.log('B') } // 打印結(jié)果:A

由于 onResolvedA 返回了一個這樣的 Promise {} 新實例,這個新實例的狀態(tài)沒有發(fā)生變化,所以不會執(zhí)行 finally  處理程序 onFinally,所以不會打印  B。這個就說明了,鏈?zhǔn)秸{(diào)用的時候處理程序的執(zhí)行是一步一步來的,只要前面的執(zhí)行完了,生成了新的實例,然后根據(jù)新實例的狀態(tài)變化,才去執(zhí)行后續(xù)的處理程序。

所以拿最開始那道題來說:

new Promise((resolve, reject) => {     resolve(1) }).then(res => {     console.log('A') }).finally(() => {     console.log('B') }) new Promise((resolve, reject) => {     resolve(2) }).then(res => {     console.log('C') }).finally(() => {     console.log('D') }) // 打印結(jié)果:A C B D

他的執(zhí)行過程應(yīng)該是這樣的:

  • 執(zhí)行 resolve(1),將處理程序 A 推入微任務(wù)隊列 1;

  • 執(zhí)行 resolve(2),將處理程序 C 推入微任務(wù)隊列 2;

  • 同步任務(wù)執(zhí)行完成,執(zhí)行微任務(wù)隊列 1 里的內(nèi)容,打印 A,A 所在函數(shù)執(zhí)行完成后生成了一個 fulfilled  的新實例,由于新實例狀態(tài)變化,所以會立即執(zhí)行 finally() 處理程序 B 推入微任務(wù)隊列 3;

  • 執(zhí)行微任務(wù)隊列 2 的內(nèi)容,打印 C,C 所在函數(shù)執(zhí)行完成后,同上條原理會將處理程序 D 推入微任務(wù)隊列 4;

  • 執(zhí)行微任務(wù)隊列 3 的內(nèi)容,打印 B;

  • 執(zhí)行微任務(wù)隊列 4 的內(nèi)容,打印 D;

  • 代碼全部執(zhí)行完成,最終打?。篈 C B D。

題目就先做到這里,相信你和我一樣,對 Promise 的執(zhí)行過程應(yīng)該有更深入的理解了。接下來我們將繼續(xù)學(xué)習(xí) Promise 的相關(guān) API。

Promise與錯誤處理

平時我們寫代碼遇到錯誤,都習(xí)慣用 try/catch 塊來處理,但是對于 Promise  產(chǎn)生的錯誤,用這個是處理不了的,看下面這段代碼:

try {     new Promise((resolve, reject) => {         console.log('A')         throw new Error()         console.log('B')     })   } catch(err) {     console.log(err) } console.log('C') // A // C  // Uncaught (in promise) Error

從執(zhí)行結(jié)果我們可以看到,報錯的信息出現(xiàn)在打印 C 之后,說明拋出錯誤這個動作是在異步任務(wù)中做的,所以 catch  捕獲不到該錯誤就在情理之中了,否則就不會打印 C 了??梢?,傳統(tǒng)的 try/catch 語句并不能捕獲 Promise 產(chǎn)生的錯誤,而需要使用  onRejected 處理程序:

let p1 = new Promise((resolve, reject) => {     console.log('A')     throw new Error('error')     console.log('B') }) let p2 = p1.catch((err) => {     console.log(err) })  setTimeout(console.log, 0, p2) // A // Error: error // Promise {<fulfilled>: undefined}

onRejected 捕獲了上面拋出的錯誤后,使得程序正常執(zhí)行,最后還生成了一個 fulfilled的新實例。

除了以上這種直接在 executor 里通過 throw 主動拋出一個錯誤外,還可以通過以下方式產(chǎn)出需要 onRejected 處理的錯誤:

new Promise((resolve, reject) => {     init() // 被動出錯,調(diào)用了不存在的函數(shù) })  new Promise((resolve, reject) => {     reject() })  new Promise((resolve, reject) => {     resolve() }).then(() => Promise.reject())  new Promise((resolve, reject) => {     resolve() }).then(() => {     throw new Error() })

注意,如果只是產(chǎn)生了一個錯誤,卻沒有拋出來是不會報錯的:

// 不會報錯 new Promise((resolve, reject) => {     reject() }).then(() => new Error())

Promise 出現(xiàn)了錯誤就需要使用 onRejected 處理程序處理,否則程序就會報錯,執(zhí)行不下去了。

Promise API

Promise.resolve()

并非所有的 Promise 的初始狀態(tài)都是 pending,可以通過 Promise.resolve(value) 來初始化一個狀態(tài)為  fulfilled,值為 value 的 Promise 實例:

let p = Promise.resolve(3) console.log(p)  // Promise {<fulfilled>: 3}

這個操作和下面這種創(chuàng)建一個 fulfilled 的 Promise 在效果上是一樣的:

let p = new Promise(resolve => resolve(3)) console.log(p)  // Promise {<fulfilled>: 3}

使用這個靜態(tài)方法,理論上可以把任何一個值轉(zhuǎn)換成 Promise:

setTimeout(console.log, 0, Promise.resolve())  // Promise {<fulfilled>: undefined} setTimeout(console.log, 0, Promise.resolve(3, 6, 9))  // Promise {<fulfilled>: 3} 多余的參數(shù)將被忽略 setTimeout(console.log, 0, Promise.resolve(new Error('error')))  // Promise {<fulfilled>: Error: error}

這個被轉(zhuǎn)換的值甚至可以是一個 Promise 對象,如果是這樣,Promise.resolve 會將其原樣輸出:

let p = Promise.resolve(3) setTimeout(console.log, 0, p === Promise.resolve(p))  // true

Promise.reject()

和 Promise.resolve() 類似,Promise.reject() 會實例化一個 rejected 狀態(tài)的  Promise,且會拋出一個錯誤,該錯誤只能通過拒絕處理程序捕獲。

Promise     .reject(3)     .catch(err => {         console.log(err)  // 3     })

對于初始化一個 rejected 狀態(tài)的實例,以下兩種寫法都可以達(dá)到這個目的:

let p1 = Promise.reject() let p2 = new Promise((resolve, reject) => reject())

與 Promise.resolve() 不同的是,如果給 Promise.reject() 傳遞一個 Promise 對象,則這個對象會成為新  Promise 的值:

let p = Promise.reject(3) setTimeout(console.log, 0, p === Promise.reject(p))  // false

Promise.all()

Promise.all(iterable) 用來將多個 Promise 實例合成一個新實例。參數(shù)必須是一個可迭代對象,通常是數(shù)組。

Promise.all([     Promise.resolve(3),     Promise.resolve(6) ])

可迭代對象里的所有元素都會通過 Promise.resolve() 轉(zhuǎn)成 Promise:

Promise.all([3, 6, 9])

所有 Promise 都 resolve 后,Promise.all() 才會生成一個 fulfilled 的新實例。且新實例的內(nèi)部值是由所有  Promise 解決后的值組成的數(shù)組:

let p1 = Promise.all([     Promise.resolve('3'),     Promise.resolve(),     6 ]) let p2 = p1.then(res => {     console.log(res) }) setTimeout(console.log, 0, p1) // ["3", undefined, 6] // Promise {<fulfilled>: Array(3)}

所有 Promise 中,只要出現(xiàn)一個 pending 狀態(tài)的實例,那么合成的新實例也是 pending 狀態(tài)的:

let p1 = Promise.all([     3,     Promise.resolve(6),     new Promise(() => {}) ]) setTimeout(console.log, 0, p1) // Promise {<pending>}

所有 Promise 中,只要出現(xiàn)一個 rejected 狀態(tài)的實例,那么合成的新實例也是 rejected狀態(tài)的,且新實例的內(nèi)部值是第一個拒絕  Promise 的內(nèi)部值:

let p1 = Promise.all([     3,     Promise.reject(6),     new Promise((resolve, reject) => {         reject(9)     }) ]) let p2 = p1.catch(err => {     console.log(err) }) setTimeout(console.log, 0, p1) // 6 // Promise {<rejected>: 6}

Promise.race()

Promise.race(iterable) 會返回一個由所有可迭代實例中第一個 fulfilled 或 rejected的實例包裝后的新實例。

let p1 = Promise.race([     3,     Promise.reject(6),     new Promise((resolve, reject) => {         resolve(9)     }).then(res => {         console.log(res)     }) ]) let p2 = p1.then(res => {     console.log(err) }) setTimeout(console.log, 0, p1) // 9 // 3 // Promise {<fulfilled>: 3}

來將上面這串代碼變動下:

function init(){     console.log(3)     return 3 } let p1 = Promise.race([     new Promise((resolve, reject) => {         resolve(9)     }).then(res => {         console.log(res)         return 'A'     }),     new Promise((resolve, reject) => {         reject(6)     }),     init(), ]) let p2 = p1.then(res => {     console.log(res) }, err => {     console.log(err) }) setTimeout(console.log, 0, p1) // 3 // 9 // 6 // Promise {<rejected>: 6}

想要知道 Promise.race() 的結(jié)果,無非是要知道到底誰才是第一個狀態(tài)變化的實例,讓我們來具體分析下代碼執(zhí)行過程:

  • 迭代第一個元素,執(zhí)行同步代碼 resolve(9),由 new Promise 初始化的實例的狀態(tài)已經(jīng)變?yōu)榱? fulfilled,所以第一個狀態(tài)變化的實例已經(jīng)出現(xiàn)了嗎?其實并沒有,因為迭代第一個元素的代碼還沒執(zhí)行完成呢,然后會將 return 'A'  所在函數(shù)的這段處理程序推入微任務(wù)隊列 1;

  • 迭代第二個元素,執(zhí)行 reject(6),所以由 new Promise 初始化的實例的狀態(tài)已經(jīng)變?yōu)? rejected,由于該實例沒有處理函數(shù),所以迭代第二個元素的代碼已經(jīng)全部執(zhí)行完成,此時,第一個狀態(tài)變化的實例已經(jīng)產(chǎn)生;

  • 迭代第三個元素,是一個函數(shù),執(zhí)行同步代碼打印出 3,然后用 Promise.resolve 將函數(shù)返回值 3 轉(zhuǎn)成一個 Promise  {

    : 3} 的新實例,這是第二個狀態(tài)發(fā)生變化的實例;
  • 此時所有迭代對象遍歷完成,即同步代碼執(zhí)行完成,開始執(zhí)行微任務(wù)隊列 1 的內(nèi)容,打印 res,其值是 9,然后處理程序返回了  'A',此時根據(jù)之前提到的知識點,這里會新生成一個 Promise {

    : 'A'}  的實例,這是第三個狀態(tài)發(fā)生變化的實例。此時,第一個迭代元素的代碼已經(jīng)全部執(zhí)行完成,所以第一個迭代元素最終生成的實例是第三次狀態(tài)發(fā)生變化的這個;
  • 此時 p1 已經(jīng)產(chǎn)生,它是 Promise {

    : 6},所以會將它的處理程序 console.log(err) 所在函數(shù)推入微任務(wù)隊列  2;
  • 執(zhí)行微任務(wù)隊列 2 的內(nèi)容,打印 err,其值是 6;

  • 所有微任務(wù)執(zhí)行完成,開始執(zhí)行 setTimeout 里的宏任務(wù),打印 p1,至此全部代碼執(zhí)行完成。

Promise.allSettled()

Promise.allSettled(iterable) 當(dāng)所有的實例都已經(jīng)  settled,即狀態(tài)變化過了,那么將返回一個新實例,該新實例的內(nèi)部值是由所有實例的值和狀態(tài)組合成的數(shù)組,數(shù)組的每項是由每個實例的狀態(tài)和值組成的對象。

function init(){     return 3 } let p1 = Promise.allSettled([     new Promise((resolve, reject) => {         resolve(9)     }).then(res => {}),     new Promise((resolve, reject) => {         reject(6)     }),     init() ]) let p2 = p1.then(res => {     console.log(res) }, err => {     console.log(err) }) // [ //      {status: "fulfilled", value: undefined},  //      {status: "rejected", reason: 6},  //      {status: "fulfilled", value: 3} // ]

只要所有實例中包含一個 pending 狀態(tài)的實例,那么 Promise.allSettled() 的結(jié)果為返回一個這樣 Promise  {} 的實例。

?Promise.allSettled() 是 ES2020 中新增的方法,所以有一些瀏覽器可能還暫時不支持。?

對于不支持的瀏覽器,可以寫 polyfill:

if(!Promise.allSettled) {     Promise.allSettled = function(promises) {         return Promise.all(promises.map(p => Promise.resolve(p)             .then(value => ({                 status: 'fulfilled',                 value             }), reason => ({                 status: 'rejected',                 reason             }))         ));     } }

感謝各位的閱讀,以上就是“Promise的概念和作用是什么”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對Promise的概念和作用是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

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

AI