前言:javascript
一直以來都是隻會調用Promise的API,並且調API仍是調用axios封裝好的Promise,太丟人了!!!沒有真正的去了解過它的原理是如何實現的,本身也看過不少博主實現的Promise,但總以爲用原型鏈的OOP晦澀難懂。java
我的的理解:若是帶着觀察者模式的想法來理解Promise源碼,你就會發現Promise自己其實一種微任務的觀察者模式,一個異步任務的完成,res/rej
的狀態回調hook => 通知全部then()
訂閱的promise對象。promise只是將觀察者模式運用到微任務。讓promise對象可以具備很高的優先級。說到底仍是一種解藕的設計模式。jquery
在瞭解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
問題出來了,那解決的思路也有了: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是帶有 resolve
和 reject
兩個參數的函數 。Promise構造函數執行時當即調用executor
函數, resolve
和 reject
兩個函數做爲參數傳遞給executor
(executor 函數在Promise構造函數返回所建promise實例對象前被調用)
回到主題,我以爲先介紹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(); };
按照原生的Promise.then()方法的邏輯來說,原Promise的狀態會直接影響到then
方法返回的Promise的狀態,所以設置狀態的resolve
和reject
函數邏輯以下:
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幫助咱們作了什麼吧!
then
方法 => 建立一個新的promise實例賦於給promise2,而且新的promise實例的executor
執行promise1的attachHandler
,將then函數中的回調函數對象push進promise1的handlers
屬性中,若是promise1已是settled狀態,直接更加promise1的狀態來執行不一樣函數handlers
數組中有兩個持有回調函數的對象,這兩個Promise3和promise2都等着promise1的setResult
來執行相應的回調,所以promise3和promise此時屬於pending狀態res/rej
狀態執行handlers中的全部then添加的回調,Promise類的catch
和finally
都是在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