成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一篇帶你深入理解Promise

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

[[354385]]

 從異步編程說起

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

對(duì)于同步行為,如下面的代碼,我們能夠很清楚的知道每一行會(huì)發(fā)生什么,這是因?yàn)楹竺娴闹噶羁偸堑鹊角懊娴闹噶顖?zhí)行完成后才去執(zhí)行,所以這里的第二行里的變量 x 在內(nèi)存里已經(jīng)是定義過的。

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

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

  1. function loadScript(src) { 
  2.     let script = document.createElement('script'
  3.     script.src = src 
  4.     document.head.append(script) 

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

  1. function loadScript(src) { 
  2.     let script = document.createElement('script'
  3.     script.src = src 
  4.     document.head.append(script) 
  5. loadScript('./js/script.js'
  6. init()  // 定義在 ./js/script.js 里的函數(shù) 

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

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

  1. function loadScript(src, success, fail) { 
  2.     let script = document.createElement('script'
  3.     script.src = src 
  4.     script.onload = success 
  5.     script.onerror = fail 
  6.     document.head.append(script) 
  7. loadScript('./js/script.js', success, fail) 
  8. function success() { 
  9.     console.log('success'
  10.     init()  // 定義在 ./js/script.js 中的函數(shù) 
  11. function fail() { 
  12.     console.log('fail'

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

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

Promise正篇

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

  1. function loadScript(src) { 
  2.     return new Promise(function(resolve, reject) { 
  3.         let script = document.createElement('script'); 
  4.         script.src = src; 
  5.         script.onload = () => resolve(script); 
  6.         script.onerror = () => reject(new Error(`Script load error for ${src}`)); 
  7.         document.head.append(script); 
  8.     }); 
  9. loadScript('./scripts.js').then(res => { 
  10.     console.log('success', res); 
  11.     init() 
  12. }).catch(err => { 
  13.     console.log(err); 
  14. }) 

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

  • Promise 首先是一個(gè)對(duì)象,它通常用于描述現(xiàn)在開始執(zhí)行,一段時(shí)間后才能獲得結(jié)果的行為(異步行為),內(nèi)部保存了該異步行為的結(jié)果。然后,它還是一個(gè)有狀態(tài)的對(duì)象:
  • pending:待定
  • fulfilled:兌現(xiàn),有時(shí)候也叫解決(resolved)
  • rejected:拒絕
  • 一個(gè) Promise 只有這 3 種狀態(tài),且狀態(tài)的轉(zhuǎn)換過程有且僅有 2 種:
  • pending 到 fulfilled
  • pending 到 rejected

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

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

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

  1. new Promise((resolve, reject) => { 
  2.     setTimeout(() => { 
  3.         resolve() 
  4.     }, 1000) 
  5. }) 

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

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

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

  1. let p = new Promise((resolve, reject) => { 
  2.     setTimeout(() => { 
  3.         resolve('第一次 resolve'
  4.         resolve('第二次 resolve')  // 將被忽略 
  5.         reject('第一次 reject')  // 將被忽略 
  6.     }, 0) 
  7. }) 
  8. 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 對(duì)象具有如下內(nèi)部屬性:

  • PromiseState:最初是 pending,resolve 被調(diào)用的時(shí)候變?yōu)?fulfilled,或者 reject 被調(diào)用時(shí)會(huì)變?yōu)? rejected;
  • PromiseResult:最初是 undefined,resolve(value) 被調(diào)用時(shí)變?yōu)?value,或者在 reject(error) 被調(diào)用時(shí)變?yōu)?error。

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

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

Promise實(shí)例方法

Promise.prototype.then()

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

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

它可以寫成這樣:

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

或者寫成更簡(jiǎn)單的方式:

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

因?yàn)闋顟B(tài)的變化只有 2 種,所以 onResolved 和 onRejected 在執(zhí)行的時(shí)候必定是互斥。

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

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

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

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

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

Promise.prototype.catch()

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

  1. function onRejected(err){} 
  2. new Promise((resolve, reject) => { 
  3.     reject() 
  4. }).catch(onRejected) 
  5. new Promise((resolve, reject) => { 
  6.     reject() 
  7. }).then(null, onRejected) 

Promise.prototype.finally()

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

  1. function onFinally() { 
  2.     console.log(888)  // 并不會(huì)執(zhí)行   
  3. new Promise((resolve, reject) => { 
  4.      
  5. }).finally(onFinally) 

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

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

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

  1. new Promise((resolve, reject) => { 
  2.     resolve() 
  3. }).then(() => { 
  4.     console.log('A'
  5.     new Promise((resolve, reject) => { 
  6.         resolve() 
  7.     }).then(() => { 
  8.         console.log('B'
  9.     }).then(() => { 
  10.         console.log('C'
  11.     }) 
  12. }).then(() => { 
  13.     console.log('D'
  14. }) 

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

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

基于onResolved生成一個(gè)新的Promise

因?yàn)?Promise.prototype.then() 會(huì)返回一個(gè)新的 Promise,來看下:

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

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

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

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

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

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

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

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

  1. let p1 = new Promise((resolve, reject) => { 
  2.     resolve(3) 
  3. }) 
  4. let p2 = p1.then(() => { 
  5.     throw new Error('這是一個(gè)錯(cuò)誤')} 
  6. setTimeout(console.log, 0, p2)  // Promise {<rejected>: 這是一個(gè)錯(cuò)誤} 

基于onRejected生成一個(gè)新的Promise

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

  1. let p1 = new Promise((resolve, reject) => { 
  2.     reject(3) 
  3. }) 
  4.  
  5. // 沒有 `onRejected` 處理程序時(shí),會(huì)原樣向后傳,不過是新實(shí)例 
  6. let p2 = p1.then(() => {})  s 
  7. setTimeout(console.log, 0, p2)  // Promise {<rejected>: 3} 
  8.  
  9. // 返回值為undefined時(shí) 
  10. let p3 = p1.then(null, () => {})  
  11. setTimeout(console.log, 0, p3)  // Promise {<fulfilled>: undefined}  
  12.  
  13. // 返回值有實(shí)際值的時(shí)候 
  14. let p4 = p1.then(null, () => 6)  
  15. setTimeout(console.log, 0, p4)  // Promise {<fulfilled>: 6} 
  16.  
  17. // 當(dāng)返回值是Promise時(shí),會(huì)保留當(dāng)前Promise 
  18. let p5 = p1.then(null, () => Promise.reject())  
  19. setTimeout(console.log, 0, p5)  // Promise {<rejected>: undefined}  
  20.  
  21. // 當(dāng)遇到一個(gè)錯(cuò)誤的時(shí)候 
  22. let p6 = p1.then(null, () => { 
  23.     throw new Error('error'
  24. })  
  25. setTimeout(console.log, 0, p6)  // Promise {<rejected>: error}  
  26.  
  27. // 當(dāng)返回值是一個(gè)錯(cuò)誤時(shí) 
  28. let p7 = p1.then(null, () => new Error('error'))  
  29. setTimeout(console.log, 0, p7)  // Promise {<fulfilled>: Error: error}  

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

  1. new Promise((resolve, reject) => { 
  2.     reject() 
  3. }).then(null, () => { 
  4.     console.log('A'
  5. }).then(() => { 
  6.     console.log('B'
  7. }).then(() => { 
  8.     console.log('C'
  9. }).catch(() => { 
  10.     console.log('D'
  11. }) 

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

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

finally生成一個(gè)新的Promise

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

  1. let p1 = new Promise(() => {}) 
  2. let p2 = p1.finally(() => {}) 
  3. setTimeout(console.log, 0, p2)  // Promise {<pending>} 
  4.  
  5. let p3 = new Promise((resolve, reject) => { 
  6.     resolve(3) 
  7. }) 
  8. let p4 = p3.finally(() => {}) 
  9. setTimeout(console.log, 0, p3)  // Promise {<fulfilled>: 3} 

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

  1. let p1 = new Promise((resolve, reject) => { 
  2.     resolve(3) 
  3. }) 
  4. let p2 = p1.finally(() => new Promise(() => {})) 
  5. setTimeout(console.log, 0, p2)  // Promise {<pending>} 
  6.  
  7. let p3 = p1.finally(() => Promise.reject(6)) 
  8. setTimeout(console.log, 0, p3)  // Promise {<rejected>: 6} 
  9.  
  10. let p4 = p1.finally(() => { 
  11.     throw new Error('error'
  12. }) 
  13. setTimeout(console.log, 0, p4)  // Promise {<rejected>: Error: error} 

執(zhí)行順序

先來看一段簡(jiǎn)單的代碼:

  1. new Promise((resolve, reject) => { 
  2.     console.log('A'
  3.     resolve(3) 
  4.     console.log('B'
  5. }).then(res => { 
  6.     console.log('C'
  7. }) 
  8. console.log('D'
  9. // 打印結(jié)果:A B D C 

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

  • resolve() 對(duì)應(yīng) .then() 里的第一個(gè)入?yún)ⅲ?onResolved 函數(shù);
  • reject() 對(duì)應(yīng) .then() 里的第二個(gè)入?yún)ⅲ?onRejected 函數(shù);或者 Promise.prototype.catch() 里的回調(diào)函數(shù);

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

  1. let onResolved = res => { 
  2.     console.log('C'

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

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

Promise 鏈?zhǔn)秸{(diào)用的基礎(chǔ)就是因?yàn)?onResolved、onRejected、catch() 的處理程序以及 onFinally 會(huì)產(chǎn)生一個(gè)新的 Promise 實(shí)例,且又因?yàn)樗麄兌际钱惒綀?zhí)行的,所以在鏈?zhǔn)秸{(diào)用的時(shí)候,對(duì)于它們執(zhí)行順序會(huì)稀里糊涂琢磨不透就是這個(gè)原因。

題目一

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

  1. new Promise((resolve, reject) => { 
  2.     resolve() 
  3. }).then(() => { 
  4.     console.log('A'
  5.     new Promise((resolve, reject) => { 
  6.         resolve() 
  7.     }).then(() => { 
  8.         console.log('B'
  9.     }).then(() => { 
  10.         console.log('C'
  11.     }) 
  12. }).then(() => { 
  13.     console.log('D'
  14. }) 
  15. // 打印結(jié)果:A 

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

  1. new Promise(executor).then(onResolvedA).then(onResolvedD) 
  2.  
  3. function executor(resolve, reject) { 
  4.     resolve() 
  5. function onResolvedA() { 
  6.     console.log('A'
  7.     new Promise(executor).then(onResolvedB).then(onResolvedC) 
  8. function onResolvedB() { 
  9.     console.log('B'
  10. function onResolvedC() { 
  11.     console.log('C'
  12. function onResolvedD() { 
  13.     console.log('D'

執(zhí)行過程:

  • 執(zhí)行 new Promise(),立即同步執(zhí)行 executor 函數(shù),調(diào)用 resolve(),此時(shí)會(huì)將 onResolvedA 推入微任務(wù)隊(duì)列 1,截止目前所有同步代碼執(zhí)行完成;
  • 檢查微任務(wù)隊(duì)列,執(zhí)行 onResolvedA 函數(shù),打印 A,執(zhí)行 new Promise(executor),調(diào)用 resolve() 函數(shù),此時(shí)將 onResolvedB 推入微任務(wù)隊(duì)列 2;
  • 截止目前微任務(wù)隊(duì)列 1 的代碼全部執(zhí)行完成,即 onResolvedA 函數(shù)執(zhí)行完成。我們知道 onResolved 函數(shù)會(huì)基于返回值生成一個(gè)新的 Promise,而 onResolvedA 函數(shù)沒有顯示的返回值,所以其返回值為 undefined,那么經(jīng)過 Promise.resolve(undefined) 初始化后會(huì)生成一個(gè)這樣的新實(shí)例:Promise {: undefined};由于這個(gè)新的實(shí)例狀態(tài)已經(jīng)變成 fulfilled,所以會(huì)立即將其處理函數(shù) onResolvedD 推入微任務(wù)隊(duì)列 3;
  • 開始執(zhí)行微任務(wù)隊(duì)列 2 里的內(nèi)容,打印 B,同上一條原理,由于 onResolvedB 函數(shù)的返回值為 undefined,所以生成了一個(gè) resolved 的新實(shí)例,則會(huì)立即將 onResolvedC 推入微任務(wù)隊(duì)列 4;
  • 執(zhí)行微任務(wù)隊(duì)列 3,打印 D;
  • 執(zhí)行微任務(wù)隊(duì)列 4,打印 C;
  • 至此全部代碼執(zhí)行完成,最終的打印結(jié)果為:A B D C。

題目二

  1. new Promise((resolve, reject) => { 
  2.     resolve(1) 
  3. }).then(res => { 
  4.     console.log('A'
  5. }).finally(() => { 
  6.     console.log('B'
  7. }) 
  8. new Promise((resolve, reject) => { 
  9.     resolve(2) 
  10. }).then(res => { 
  11.     console.log('C'
  12. }).finally(() => { 
  13.     console.log('D'
  14. }) 
  15. // 打印結(jié)果:A C B D 

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

  1. new Promise((resolve, reject) => { 
  2.     resolve(1) 
  3. }).then(onResolvedA).finally(onFinally) 
  4.  
  5. function onResolvedA() { 
  6.     console.log('A'
  7. function onFinally() { 
  8.     console.log('B'
  9. // 打印結(jié)果:A B 

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

  1. new Promise((resolve, reject) => { 
  2.     resolve(1) 
  3. }).then(onResolvedA).finally(onFinally) 
  4.  
  5. function onResolvedA() { 
  6.     console.log('A'
  7.     return new Promise(() => {}) 
  8. function onFinally() { 
  9.     console.log('B'
  10. // 打印結(jié)果:A 

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

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

  1. new Promise((resolve, reject) => { 
  2.     resolve(1) 
  3. }).then(res => { 
  4.     console.log('A'
  5. }).finally(() => { 
  6.     console.log('B'
  7. }) 
  8. new Promise((resolve, reject) => { 
  9.     resolve(2) 
  10. }).then(res => { 
  11.     console.log('C'
  12. }).finally(() => { 
  13.     console.log('D'
  14. }) 
  15. // 打印結(jié)果:A C B D 

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

  • 執(zhí)行 resolve(1),將處理程序 A 推入微任務(wù)隊(duì)列 1;
  • 執(zhí)行 resolve(2),將處理程序 C 推入微任務(wù)隊(duì)列 2;
  • 同步任務(wù)執(zhí)行完成,執(zhí)行微任務(wù)隊(duì)列 1 里的內(nèi)容,打印 A,A 所在函數(shù)執(zhí)行完成后生成了一個(gè) fulfilled 的新實(shí)例,由于新實(shí)例狀態(tài)變化,所以會(huì)立即執(zhí)行 finally() 處理程序 B 推入微任務(wù)隊(duì)列 3;
  • 執(zhí)行微任務(wù)隊(duì)列 2 的內(nèi)容,打印 C,C 所在函數(shù)執(zhí)行完成后,同上條原理會(huì)將處理程序 D 推入微任務(wù)隊(duì)列 4;
  • 執(zhí)行微任務(wù)隊(duì)列 3 的內(nèi)容,打印 B;
  • 執(zhí)行微任務(wù)隊(duì)列 4 的內(nèi)容,打印 D;
  • 代碼全部執(zhí)行完成,最終打印:A C B D。

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

Promise與錯(cuò)誤處理

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

  1. try { 
  2.     new Promise((resolve, reject) => { 
  3.         console.log('A'
  4.         throw new Error() 
  5.         console.log('B'
  6.     })   
  7. } catch(err) { 
  8.     console.log(err) 
  9. console.log('C'
  10. // A 
  11. // C  
  12. // Uncaught (in promise) Error 

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

  1. let p1 = new Promise((resolve, reject) => { 
  2.     console.log('A'
  3.     throw new Error('error'
  4.     console.log('B'
  5. }) 
  6. let p2 = p1.catch((err) => { 
  7.     console.log(err) 
  8. })  
  9. setTimeout(console.log, 0, p2) 
  10. // A 
  11. // Error: error 
  12. // Promise {<fulfilled>: undefined} 

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

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

  1. new Promise((resolve, reject) => { 
  2.     init() // 被動(dòng)出錯(cuò),調(diào)用了不存在的函數(shù) 
  3. }) 
  4.  
  5. new Promise((resolve, reject) => { 
  6.     reject() 
  7. }) 
  8.  
  9. new Promise((resolve, reject) => { 
  10.     resolve() 
  11. }).then(() => Promise.reject()) 
  12.  
  13. new Promise((resolve, reject) => { 
  14.     resolve() 
  15. }).then(() => { 
  16.     throw new Error() 
  17. }) 

注意,如果只是產(chǎn)生了一個(gè)錯(cuò)誤,卻沒有拋出來是不會(huì)報(bào)錯(cuò)的:

  1. // 不會(huì)報(bào)錯(cuò) 
  2. new Promise((resolve, reject) => { 
  3.     reject() 
  4. }).then(() => new Error()) 

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

Promise API

Promise.resolve()

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

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

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

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

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

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

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

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

Promise.reject()

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

  1. Promise 
  2.     .reject(3) 
  3.     .catch(err => { 
  4.         console.log(err)  // 3 
  5.     }) 

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

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

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

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

Promise.all()

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

  1. Promise.all([ 
  2.     Promise.resolve(3), 
  3.     Promise.resolve(6) 
  4. ]) 

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

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

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

  1. let p1 = Promise.all([ 
  2.     Promise.resolve('3'), 
  3.     Promise.resolve(), 
  4.     6 
  5. ]) 
  6. let p2 = p1.then(res => { 
  7.     console.log(res) 
  8. }) 
  9. setTimeout(console.log, 0, p1) 
  10. // ["3", undefined, 6] 
  11. // Promise {<fulfilled>: Array(3)} 

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

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

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

  1. let p1 = Promise.all([ 
  2.     3, 
  3.     Promise.reject(6), 
  4.     new Promise((resolve, reject) => { 
  5.         reject(9) 
  6.     }) 
  7. ]) 
  8. let p2 = p1.catch(err => { 
  9.     console.log(err) 
  10. }) 
  11. setTimeout(console.log, 0, p1) 
  12. // 6 
  13. // Promise {<rejected>: 6} 

Promise.race()

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

  1. let p1 = Promise.race([ 
  2.     3, 
  3.     Promise.reject(6), 
  4.     new Promise((resolve, reject) => { 
  5.         resolve(9) 
  6.     }).then(res => { 
  7.         console.log(res) 
  8.     }) 
  9. ]) 
  10. let p2 = p1.then(res => { 
  11.     console.log(err) 
  12. }) 
  13. setTimeout(console.log, 0, p1) 
  14. // 9 
  15. // 3 
  16. // Promise {<fulfilled>: 3} 

來將上面這串代碼變動(dòng)下:

  1. function init(){ 
  2.     console.log(3) 
  3.     return 3 
  4. let p1 = Promise.race([ 
  5.     new Promise((resolve, reject) => { 
  6.         resolve(9) 
  7.     }).then(res => { 
  8.         console.log(res) 
  9.         return 'A' 
  10.     }), 
  11.     new Promise((resolve, reject) => { 
  12.         reject(6) 
  13.     }), 
  14.     init(), 
  15. ]) 
  16. let p2 = p1.then(res => { 
  17.     console.log(res) 
  18. }, err => { 
  19.     console.log(err) 
  20. }) 
  21. setTimeout(console.log, 0, p1) 
  22. // 3 
  23. // 9 
  24. // 6 
  25. // Promise {<rejected>: 6} 

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

  • 迭代第一個(gè)元素,執(zhí)行同步代碼 resolve(9),由 new Promise 初始化的實(shí)例的狀態(tài)已經(jīng)變?yōu)榱? fulfilled,所以第一個(gè)狀態(tài)變化的實(shí)例已經(jīng)出現(xiàn)了嗎?其實(shí)并沒有,因?yàn)榈谝粋€(gè)元素的代碼還沒執(zhí)行完成呢,然后會(huì)將 return 'A' 所在函數(shù)的這段處理程序推入微任務(wù)隊(duì)列 1;
  • 迭代第二個(gè)元素,執(zhí)行 reject(6),所以由 new Promise 初始化的實(shí)例的狀態(tài)已經(jīng)變?yōu)? rejected,由于該實(shí)例沒有處理函數(shù),所以迭代第二個(gè)元素的代碼已經(jīng)全部執(zhí)行完成,此時(shí),第一個(gè)狀態(tài)變化的實(shí)例已經(jīng)產(chǎn)生;
  • 迭代第三個(gè)元素,是一個(gè)函數(shù),執(zhí)行同步代碼打印出 3,然后用 Promise.resolve 將函數(shù)返回值 3 轉(zhuǎn)成一個(gè) Promise {: 3} 的新實(shí)例,這是第二個(gè)狀態(tài)發(fā)生變化的實(shí)例;
  • 此時(shí)所有迭代對(duì)象遍歷完成,即同步代碼執(zhí)行完成,開始執(zhí)行微任務(wù)隊(duì)列 1 的內(nèi)容,打印 res,其值是 9,然后處理程序返回了 'A',此時(shí)根據(jù)之前提到的知識(shí)點(diǎn),這里會(huì)新生成一個(gè) Promise {: 'A'} 的實(shí)例,這是第三個(gè)狀態(tài)發(fā)生變化的實(shí)例。此時(shí),第一個(gè)迭代元素的代碼已經(jīng)全部執(zhí)行完成,所以第一個(gè)迭代元素最終生成的實(shí)例是第三次狀態(tài)發(fā)生變化的這個(gè);
  • 此時(shí) p1 已經(jīng)產(chǎn)生,它是 Promise {: 6},所以會(huì)將它的處理程序 console.log(err) 所在函數(shù)推入微任務(wù)隊(duì)列 2;
  • 執(zhí)行微任務(wù)隊(duì)列 2 的內(nèi)容,打印 err,其值是 6;
  • 所有微任務(wù)執(zhí)行完成,開始執(zhí)行 setTimeout 里的宏任務(wù),打印 p1,至此全部代碼執(zhí)行完成。

Promise.allSettled()

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

  1. function init(){ 
  2.     return 3 
  3. let p1 = Promise.allSettled([ 
  4.     new Promise((resolve, reject) => { 
  5.         resolve(9) 
  6.     }).then(res => {}), 
  7.     new Promise((resolve, reject) => { 
  8.         reject(6) 
  9.     }), 
  10.     init() 
  11. ]) 
  12. let p2 = p1.then(res => { 
  13.     console.log(res) 
  14. }, err => { 
  15.     console.log(err) 
  16. }) 
  17. // [ 
  18. //      {status: "fulfilled", value: undefined},  
  19. //      {status: "rejected", reason: 6},  
  20. //      {status: "fulfilled", value: 3} 
  21. // ] 

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

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

對(duì)于不支持的瀏覽器,可以寫 polyfill:

  1. if(!Promise.allSettled) { 
  2.     Promise.allSettled = function(promises) { 
  3.         return Promise.all(promises.map(p => Promise.resolve(p) 
  4.             .then(value => ({ 
  5.                 status: 'fulfilled'
  6.                 value 
  7.             }), reason => ({ 
  8.                 status: 'rejected'
  9.                 reason 
  10.             })) 
  11.         )); 
  12.     } 

感謝閱讀

首先感謝你閱讀本文,相信你付出的時(shí)間值得擁有這份回報(bào)。

 

責(zé)任編輯:姜華 來源: 大海我來了
相關(guān)推薦

2020-12-29 05:35:43

FlinkSQL排序

2022-03-10 08:31:51

REST接口規(guī)范設(shè)計(jì)Restful架構(gòu)

2019-10-11 08:41:35

JVM虛擬機(jī)語言

2025-01-09 11:26:47

2018-01-22 17:02:48

Python字符編碼ASCII

2022-12-20 08:22:42

CommitMuation

2021-09-08 17:42:45

JVM內(nèi)存模型

2020-03-18 13:40:03

Spring事數(shù)據(jù)庫(kù)代碼

2024-09-02 14:12:56

2017-11-20 11:05:23

數(shù)據(jù)庫(kù)MongoDB索引

2021-10-15 07:57:04

Docker 日志容器

2018-11-21 08:00:05

Dubbo分布式系統(tǒng)

2019-03-18 09:50:44

Nginx架構(gòu)服務(wù)器

2021-04-25 10:45:59

Docker架構(gòu)Job

2023-04-20 08:00:00

ES搜索引擎MySQL

2021-05-20 06:57:16

RabbitMQ開源消息

2022-11-21 09:09:08

Linux物理內(nèi)存管理

2019-12-06 09:44:27

HTTP數(shù)據(jù)安全

2010-06-01 15:25:27

JavaCLASSPATH

2016-12-08 15:36:59

HashMap數(shù)據(jù)結(jié)構(gòu)hash函數(shù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 91在线视频在线观看 | 欧美白人做受xxxx视频 | 国产在线视频一区二区 | 香蕉婷婷 | 久久久一二三区 | 91精品国产综合久久久久久首页 | 欧美video | www.久| 国产精品免费一区二区三区 | 伊人性伊人情综合网 | 久久国产精品免费 | 亚洲精品成人av久久 | aaaa一级毛片 | 日韩 欧美 二区 | 久久久久9999 | 在线看av网址 | 国产午夜高清 | 国产在线播 | 亚洲精品一二区 | 97精品久久| 亚洲欧洲中文日韩 | 99reav| 国产精品999 | 国产在线视频一区 | 亚洲一区影院 | 中文字幕av网| 午夜色婷婷 | 精品久久久久久亚洲国产800 | 最新超碰 | 一级黄色播放 | 日韩在线不卡 | 精品亚洲永久免费精品 | 国产欧美精品一区二区三区 | 国产一区二区三区久久久久久久久 | 久久久久国产一区二区三区四区 | 天堂av影院 | www.99re | 91久久久久久久久久久 | 欧美高清hd| 亚洲欧美日韩精品久久亚洲区 | 亚洲日本三级 |