70行實現Promise核心源碼

70行實現Promise核心源碼

前言:javascript

​ 一直以來都是隻會調用Promise的API,並且調API仍是調用axios封裝好的Promise,太丟人了!!!沒有真正的去了解過它的原理是如何實現的,本身也看過不少博主實現的Promise,但總以爲用原型鏈的OOP晦澀難懂。java

我的的理解:若是帶着觀察者模式的想法來理解Promise源碼,你就會發現Promise自己其實一種微任務的觀察者模式,一個異步任務的完成,res/rej的狀態回調hook => 通知全部then()訂閱的promise對象。promise只是將觀察者模式運用到微任務。讓promise對象可以具備很高的優先級。說到底仍是一種解藕的設計模式。jquery

promise是誕生的緣由?

​ 在瞭解Promise以前,我以爲有必要去了解一下Promise誕生的緣由。 直接就那上面的axios來講吧,之前沒有出現axios的時候,你們是怎麼去與後臺接口作交互的呢? 我當時是用jQuery封裝好的AJAX去作的。下面有一個例子ios

$.ajax({
    type: 'POST', //GET or POST url: "jquery-ajax", cache: false, data: {todo:"ajaxexample1"}, success: functionSucceed, error: functionFailed, statusCode: { 404: function() { alert("page not found"); } } }); 

若是是單獨的一個請求還好,可是若是得發送兩個相互依賴的請求呢?這時候就會出現回調地獄的問題,不能自拔。如下就是一個簡單的例子。git

a(function (result1) { b(result1,function (result2) { c(result2, function (result3) { d(result3, function (result4) { e(result4, function (result5) { console.log(result5) }) }) }) }) }) 

假如說讓你去維護一個這樣的代碼... 懼怕的兄弟萌把懼怕打在評論區[doge]。上面的代碼有什麼問題呢?es6

  • 嵌套調用,下面的任務依賴上個任務的請求結果。若是2層仍是容易理順邏輯,可是一旦出現層數過多,可讀性就會變得很是差,就像一坨屎
  • 任務的不肯定性。每個任務會有成功和失敗兩種狀態,就拿上面的代碼,假如說有5層的嵌套,就要作5次的成功、失敗的判斷函數,明顯的增長了代碼的複雜度,不符合Unix哲學。

用Typescript實現MyPromise

問題出來了,那解決的思路也有了:github

  • 消滅嵌套
  • 合併多個錯誤

設計一個對象實現上面兩個功能,使用TypeScript的OOP相比於用原型鏈來實現會更加的容易理解。在實現Promise源碼以前,對於Promise的用法、基本定義必定要有一個全方面的認知,否則去了解Promise也艱深晦澀。能夠先去看看MDN對於Promise的本質定義ajax

定義基本的屬性和構造函數

Promise有三種狀態:pending、 resolve、reject 對應了 等待、成功、失敗,表示一個異步任務的狀態是怎麼樣的。axios

enum States { PENDING = 'PENDING', RESOLVED = 'RESOLVED', REJECTED = 'REJECTED' } class MyPromise { private state: States = States.PENDING; private handlers: any[] = []; private value: any; constructor(executor: (resolve, reject) => void) { try { executor(this.resolve, this.reject); } catch (e) { this.reject(e); } } } 

handlers數組是表示當調用了then()方法時,向handlers添加回調函數。好比如下的狀況,handlers中就會有兩個回調函數,等待Promise的resolve/reject設置狀態以後,調用handlers裏的全部回調函數。設計模式

let promise1 = new MyPromise(test); let promise2 = promise1 .then(res => { // <=== 匿名回調函數 console.log(res); return 2; }); let promise3 = promise1 .then(res => { // <=== 匿名回調函數 setTimeout(() => { console.log(res + '***********************'); return 4; }, 1000); }) 

value表示的一個異步函數返回值。

executor是帶有 resolvereject 兩個參數的函數 。Promise構造函數執行時當即調用executor 函數, resolvereject 兩個函數做爲參數傳遞給executor(executor 函數在Promise構造函數返回所建promise實例對象前被調用)

回到主題,我以爲先介紹then()方法是如何實現的比較合適

實現then()

then(onSuccess?, onFail?) {
    return new MyPromise((resolve, reject) => { return this.attachHandler({ onSuccess: result => { if (!onSuccess) return resolve(result); try { return resolve(onSuccess(result)); } catch (e) { return reject(e); } }, onFail: reason => { if (!onFail) return reject(reason); try { return resolve(onFail(reason)); } catch (e) { return reject(e); } } }); }); } 

then方法的工做原理:返回一個新的Promise對象,且向原Promise對象中的handlers添加一個包含回調函數的對象,若是Promise處於Settled狀態,那就直接執行回調函數,不然,得等待Promise的狀態設置。

private execHandlers = () => { if (this.state === States.PENDING) return; this.handlers.forEach(handler => { if (this.state === States.REJECTED) { return handler.onFail(this.value); } return handler.onSuccess(this.value); }); this.handlers = []; }; private attachHandler = (handler: any) => { this.handlers.push(handler); this.execHandlers(); }; 

實現resolve和reject回調函數

按照原生的Promise.then()方法的邏輯來說,原Promise的狀態會直接影響到then方法返回的Promise的狀態,所以設置狀態的resolvereject函數邏輯以下:

private resolve = value => this.setResult(value, States.RESOLVED); private reject = value => this.setResult(value, States.REJECTED); private setResult = (value, state: States) => { const set = () => { if (this.state !== States.PENDING) return; this.value = value; this.state = state; return this.execHandlers(); }; setTimeout(set, 0); }; 

由於沒法實現真正的Promise的微任務,所以只可以經過setTimeout(fn,0),勉強來模擬實現

private resolve = value => this.setResult(value, States.RESOLVED); private reject = value => this.setResult(value, States.REJECTED); private setResult = (value, state: States) => { const set = () => { if (this.state !== States.PENDING) return; this.value = value; this.state = state; return this.execHandlers(); }; setTimeout(set, 0); }; 

離真正的微任務在一些特別的代碼上仍是有很大差距的,由於setTimeout是宏任務,在execHandlers方法中經過foreach 執行本次Promise的handlers中的回調函數時,處於同一個事件循環,如下的代碼就會和真正的Promise有出入。

function test(res, rej) { console.log('executor'); setTimeout(() => { console.log('Timer'); res(1); }, 1000); } console.log('start'); let promise1 = new MyPromise(test); // <== 這裏替換成原生的Promise,會發現promise2狀態不一樣 let promise2 = promise1.then(res => { console.log(res); return 2; }); let promise3 = promise1.then(res => { console.log(promise2); //原生的狀態是resolve,MyPromise的狀態是pending }); console.log('end'); 

總結

就拿上面的demo來理解整個Promise幫助咱們作了什麼吧!

  • 控制檯輸出'start '
  • 建立MyPromise對象而且執行test函數,引用賦值於promise1=> 輸出'executor',向延遲隊列添加等待1s的回調函數
  • 調用promise1then方法 => 建立一個新的promise實例賦於給promise2,而且新的promise實例的executor執行promise1的attachHandler,將then函數中的回調函數對象push進promise1的handlers屬性中,若是promise1已是settled狀態,直接更加promise1的狀態來執行不一樣函數
  • promise3同promise2同樣的道理,這時promise1的handlers數組中有兩個持有回調函數的對象,這兩個Promise3和promise2都等着promise1的setResult來執行相應的回調,所以promise3和promise此時屬於pending狀態
  • 控制檯輸出'end'
  • 等待1秒,控制檯輸出'Timer' ,調用Promise1的resolve函數,向微任務隊列添加setResult狀態函數,MyPromise使用settimeout模擬微任務隊列
  • setResult狀態函數根據res/rej狀態執行handlers中的全部then添加的回調,

Promise類的catchfinally都是在then上創建的語法糖,具體你們能夠更具MDN的定義來實現,還有Promise類的靜態方法,能夠參考我本身GitHub的實現。

不斷的沉澱下來,總歸會理解一個東西存在的意義。理解了promise的原理以後,去理解其餘的底層實現有會一個很好的基礎,瞭解了Promise底層以後,深深的感覺到設計模式的強大。

若是小夥伴們以爲不錯的話,點贊支持一下嗷 鐵汁~

如下是用Typescript實現的MyPromise源代碼,不過參數並無用類型,因此稱做es6的class語法也不爲過。

enum States { PENDING = 'PENDING', RESOLVED = 'RESOLVED', REJECTED = 'REJECTED' } export class MyPromise { private state: States = States.PENDING; private handlers: any[] = []; private value: any; constructor(callback: (resolve, reject) => void) { try { callback(this.resolve, this.reject); } catch (e) { this.reject(e); } } private resolve = value => this.setResult(value, States.RESOLVED); private reject = value => this.setResult(value, States.REJECTED); private setResult = (value, state: States) => { const set = () => { if (this.state !== States.PENDING) return; this.value = value; this.state = state; return this.execHandlers(); }; setTimeout(set, 0); }; private execHandlers = () => { if (this.state === States.PENDING) return; this.handlers.forEach(handler => { if (this.state === States.REJECTED) { return handler.onFail(this.value); } return handler.onSuccess(this.value); }); this.handlers = []; }; private attachHandler = (handler: any) => { this.handlers.push(handler); this.execHandlers(); }; then(onSuccess?, onFail?) { return new MyPromise((resolve, reject) => { return this.attachHandler({ onSuccess: result => { if (!onSuccess) return resolve(result); try { return resolve(onSuccess(result)); } catch (e) { return reject(e); } }, onFail: reason => { if (!onFail) return reject(reason); try { return resolve(onFail(reason)); } catch (e) { return reject(e); } } }); }); } } 

參考連接

https://www.freecodecamp.org/news/how-to-implement-promises-in-javascript-1ce2680a7f51/

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise

相關文章
相關標籤/搜索