1. 程式人生 > >手寫Promise看著一篇就足夠了

手寫Promise看著一篇就足夠了

[TOC] ## 概要 本文主要介紹了Promise上定義的api的特性,以及手寫如何實現這些特性。目的是把學習過程中的產出以部落格的方式輸出,鞏固知識,也便於之後複習 ## 部落格思路 mdn上搜索Promise,瞭解類和api的定義: - 定義了哪些屬性,分別代表什麼含義 - api需要傳什麼引數,返回什麼值,可能丟擲什麼異常 - 看官方給出的用例,猜想內部可能的實現 - 編寫原始碼,用官方用例驗證檢視返回值是否一致 ## API的特性與手寫原始碼 ### 建構函式 - promise有狀態**pending、rejected**和**resolved**,所以應該有個變數來儲存狀態 - 建構函式引數**excutor**是個**同步執行**的回撥函式,函式執行的引數是兩個函式resolved和rejected,所以promise內部需要定義兩個函式,並且在構造行數中執行excutor的地方傳入 - .then中會傳入回撥函式onResolved和onRejected,在resolved和rejected內會分別會觸發對應的回撥函式,所以需要兩個陣列儲存then中傳進來的回撥 - resolved和rejected**只能執行一次**,執行後promise的狀態會改變,且引數會傳遞給回撥函式 - onRejected和onResolved**非同步執行** - excutor執行拋異常會直接執行rejected,所以**excutor的執行需要catch錯誤** ```javascript const PENDING = "PENDING"; const RESOLVED = "resolved"; const REJECTED = "rejected"; function MyPromise(excutor){ // promise內部儲存著狀態值 this.status = PENDING; this.data = null; // then方法傳進來的回撥函式此儲存 this.onResolvedList = []; this.onRejectedList = []; let resolved = (value) => { // resolved函式只能執行一次,所以先判斷狀態是不是pending if(this.status !== PENDING){ return; } // 變更狀態為resolved this.status = RESOLVED; // 資料為傳進來的值 this.data = value; // 判斷是否已經有onResolved回撥已經穿入,有則非同步執行 if(this.onResolvedList.length > 0){ setTimeout(() => { this.onResolvedList.forEach(onResolved => { onResolved(value); }); }, 0); } } let rejected = (reason) => { if(this.status !== PENDING){ return } this.status = REJECTED; this.data = reason; if(this.onRejectedList.length > 0){ setTimeout(() => { this.onRejectedList.forEach(onRejected => { onRejected(reason); }); }); } } try{ // 執行器函式同步執行,且引數為promise內定義的resolve和rejected excutor(resolved, rejected); }catch(error){ // 如果執行器函數出錯直接執行rejected this.rejected(error); } } ``` ### then - then會接受兩個回撥函式onResolved和onRejected - onResolved和onRejected會非同步呼叫 - then會返回一個新的promise物件 - then的引數如果沒傳,那麼value和reason繼續向下傳遞 - 如果執行then的時候,promise的狀態還是pending,那麼只儲存回撥,並且**確保回撥執行後能修改新的promise的狀態** - 如果觸發的對應的回撥函式執行拋異常,那麼返回的新的回撥函式狀態為rejected,reason則會catch到的error - 如果觸發的對應回撥函式執行返回值不是promise物件,那麼返回新的promise狀態為resolved,value則為傳入then的回撥的返回值 - 如果觸發的對應回撥返回值是promise物件,那麼新的promise返回值的狀態取決於改回調返回的promise ```javascript MyPromise.prototype.then = function(onResolved, onRejected){ // 如果沒有傳onResolved,則設定onResolved為返回value的函式 onResolved = typeof onResolved === "function" ? onResolved : value => value // 如果沒有傳onRejected,則設定onRejected為拋處reason的函式 onRejected = typeof onRejected === "function" ? onRejected : reason => {throw reason} return new MyPromise((resolved, rejected) => { // 傳入要執行的回撥函式 let callBackExcu = (callback) => { try{ let result = callback(this.data); if(result instanceof MyPromise){ // 如果回撥返回值還是promise則then返回的promise的狀態取決於回撥的返回的promise,成功就執行resolved失敗就執行rejected result.then(resolved, rejected); }else{ // 如果回撥的返回值不為promise則新的promise狀態為resolved resolved(result) } }catch(error){ // 如果回撥執行拋處異常,則新的promise狀態為rejected rejected(error); } } if(this.status === PENDING){ // 如果狀態為pending則儲存回撥且確保回撥執行後能修改當前返回的promise的狀態 this.onResolvedList.push((value) => { callBackExcu(onResolved) }); this.onRejectedList.push((reason) => { callBackExcu(onRejected) }); }else{ // 如果狀態不為pending則根據狀態執行對應的回撥,且修改當前promise的狀態 switch(this.status){ case REJECTED: // onRejected非同步執行 setTimeout(() => { callBackExcu(onRejected); }); break; case RESOLVED: // onResolved非同步執行 setTimeout(() => { callBackExcu(onResolved); }); break; } } }); } ``` ### catch catch和then其實差不多,不同點在於傳入的引數只有onRejected,所以 ```javascript MyPromise.prototype.catch = function(onRejected){ // catch與then的不同點在於傳入的引數不一樣,不需要傳onResolve return this.then(null, onRejected); } ``` ### Promise.resolved - resolved會返回一個promise物件 - 如果傳入的引數本就是一個primise物件則直接返回 - 如果是一個包含“then”方法的物件,返回新的promise物件,且狀態取決於then函式的執行,如果then的執行中拋錯,則新的promise狀態為rejected - then的引數為兩個回撥函式resolved和rejected - 如果傳入引數value既不是promise的例項,也不是具備then函式的物件,則返回一個新的promise物件且改物件data就為value ```javascript MyPromise.resolve = function(value){ if(value instanceof MyPromise){ // 如果傳入的引數本就是一個primise物件則直接返回 return value; }else if(typeof value.then === "function"){ return new MyPromise((resolved, rejected) => { try{ // then的引數為兩個回撥函式resolved和rejected value.then(resolved, rejected); }catch(error){ // 如果then的執行中拋錯,則新的promise狀態為rejected rejected(error); } }); }else{ // 如果傳入引數value既不是promise的例項 return new MyPromise((resolved, rejected) => { resolved(value); }); } } ``` ### Promise.rejected - 接受引數reason,返回一個狀態為rejected的data為reason的promise例項 ```javascript MyPromise.reject = function(reason){ return new MyPromise((resolved, rejected) => { rejected(reason); }); } ``` ### Promise.all - 接收的引數是需要滿足[可迭代協議](https://blog.csdn.net/jianghao2016/article/details/108185212),否則會拋錯 - 返回值是個promise - 如果傳入的引數是個空的可迭代的物件,則返回一個狀態為resolved的可promise例項,data為空陣列, ```javascript Promise.all([]) // P