promise 標准
在實現 Promise 之前要清楚的是 JavaScript 中的 Promise 遵循了 Promises/A+ 規范,所以我們在編寫 Promise 時也應當遵循這個規范,建議認真、仔細讀幾遍這個規范。最好是理解事件循環,這樣對於理解js中的異步是怎么回事非常重要。
基本使用
new Promise( function(resolve, reject) {...} /* executor */ );
new Promise((resolve, reject)=> { AjaxRequest.post({ url: 'url', data: {}, sueccess: ()=> { resolve(res) }, fail: (err)=> { reject(err) } }) }).then((res)=> { // do some }).then(value => { }).catch((err)=> { // do some })
promise 是處理異步結果的一個對象,承若狀態改變時調用對應的回調函數,resolve、reject用來改變promise 的狀態,then 綁定成功、失敗的回調。
環境准備
安裝測試工具以及nodemon因為我們要在node環境調試自己寫的promise
// nodemon npm install nodemon -D // promise 測試工具 npm install promises-aplus-tests -D
增加腳本命令
"testPromise": "promises-aplus-tests myPromise/promise3.js",
"dev": "nodemon ./myPromise/index.js -i "
各自的路徑改成自己的即可,這個在后面會用來測試。
基本架子
根據規范實現一個簡單的promise,功能如下
- promise的三種狀態(PENDING、FULFILLED、REJECTED)
- 狀態只能由 Pending 變為 Fulfilled 或由 Pending 變為 Rejected ,且狀態改變之后不會在發生變化,會一直保持這個狀態
- 綁定then的回調
- 返回成功、失敗的值
- 一個promise 支持調用多次then
- 支持捕獲異常
/* 基本架子 根據promise A+ 規范還要處理then鏈式調用以及返回值傳遞的問題,后續在promise2、promise3 處理 */ const PENDING = 'PENDING', FULFILLED = 'FULFILLED', REJECTED = 'REJECTED'; class myPromise { constructor (executor) { this.status = PENDING this.value = undefined this.reason = undefined
this.onResolveCallbacks = [] this.onRejectedCallbacks = [] const resolve = (value) => { if (this.status === PENDING) { this.status = FULFILLED this.value = value // 發布 this.onResolveCallbacks.forEach(fn => fn()) } } const reject = (reason) => { if (this.status === PENDING) { this.status = REJECTED this.reason = reason // 發布 this.onRejectedCallbacks.forEach(fn => fn()) } } try { // 執行傳進來的fn, 在給他提供改變狀態的fn executor(resolve, reject) } catch(e) { reject(e) } } // 訂閱回調函數 then (onFulfilled, onRejected) { if (this.status = PENDING) { // 訂閱 this.onResolveCallbacks.push(() => { onFulfilled(this.value) }) this.onRejectedCallbacks.push(() => { onRejected(this.reason) }) } if (this.status === FULFILLED) { onFulfilled(this.value) } if (this.status === REJECTED) { onRejected(this.reason) } } } module.exports = myPromise
訂閱
傳進來的fn是一個執行器,接受resolve、reject參數,通常我們在構造函數中需要調用某個接口,這是一個異步的操作,執行完構造函數之后,在執行then(),這個時候的狀態還是pending,所以我們需要把then 綁定的回調存起來,也可以理解為promise對象訂閱了這個回調。
發布
在 resolve,reject函數中中我們改變了promise 對象的狀態,既然狀態改變了,那么我們需要執行之前訂閱的回調,所以在不同的狀態下執行對應的回調即可。
流程

如上所示,實例化對象,執行構造函數,碰到異步,掛起,然后執行then()方法,綁定了resolve、reject的回調。如果異步有了結果執行對應的業務邏輯,調用resolve、或者reject,改變對應的狀態,觸發我們綁定的回調。
以上就是最基本的promise架子,但是還有promise 調用鏈沒有處理,下面繼續完善...
完善promise 調用鏈
promose 的精妙的地方就是這個調用鏈,首先then 函數會返回一個新的promise 對象,並且每一個promise 對象又有一個then 函數。驚不驚喜原理就是那么簡單,回顧下then的一些特點
then 特點
- then 返回一個新的promise 對象
- then 綁定的回調函數在異步隊列中執行(evnet loop 事件循環)
- 通過return 來傳遞結果,跟fn一樣如果沒有return,默認會是 underfined
- 拋出異常執行綁定的失敗函數(最近的promise),如果沒有,則執行catch
- then中不管是不是異步只要resolve、rejected 就會執行對應 onFulfilled、onRejected 函數
- then中返回promise狀態跟執行回調的結果有關,如果沒有異常則是FULFILLED,就算沒有retun 也是FULFILLED,值是underfined,有異常就是REJECTED,接着走下個then 綁定的onFulfilled 、onRejected 函數
根據上面的特點以及閱讀規范我們知道then()函數主要需要處理以下幾點
- 返回一個新的promise
- 值怎么傳給then返回的那個promise
- 狀態的改變
返回一個新的promise
因為promise 的鏈式調用涉及到狀態,所以then 中返回的promise 是一個新的promise
then(onFulfilled, onRejected) { let promise2 = new Promise((resolve, reject) => { // do ... }) return promise2 }
值的傳遞、狀態的改變
let p = new myPromise((resolve, rejected) => { // do ... }) p.then( value => { return 1 }, reason => {} ) .then( value => { return new Promise((resolve, rejected) => { resolve('joel') }) }, reason => {} ) .then( value => { throw 'err: 出錯啦' }, reason => {} )
then 返回的值可能是一個普通值、promise對象、function、error 等對於這部分規范文檔也有詳細的說明

[[Resolve]](promise, x)
這個可以理解為promise 處理的過程,其中x是執行回調的一個值,promise 是返回新的promise對象,完整代碼如下
我們將這部分邏輯抽成一個獨立的函數 如下
// 處理then返回結果的流程 function resolvePromise(promise2, x, resolve, reject) { if (promise2 === x) { return reject(new TypeError('Chaining cycle detected for promise #<myPromise>')) } let called = false if ((typeof x === 'object' && x !== null) || typeof x === 'function') { try { let then = x.then // 判斷是否是promise if (typeof then === 'function') { then.call(x, (y) => { // 如果 resolvePromise 以值 y 為參數被調用,則運行 [[Resolve]](promise, y) if (called) return called = true resolvePromise(promise2, y, resolve, reject) }, (r) => { if (called) return called = true reject(r) }) } else { resolve(x) } } catch (e) { if (called) return called = true reject(e) } } else { // 如果 x 不為對象或者函數,以 x 普通值執行回調 resolve(x) } }
測試
promises-aplus-tests 這個工具我們必須實現一個靜態方法deferred,官方對這個方法的定義如下:
deferred: 返回一個包含{ promise, resolve, reject }的對象
promise 是一個處於pending狀態的promise
resolve(value) 用value解決上面那個promise
reject(reason) 用reason拒絕上面那個promise
添加如下代碼
myPromise.defer = myPromise.deferred = function () { let deferred = {} deferred.promise = new myPromise((resolve, reject) => { deferred.resolve = resolve deferred.reject = reject }) return deferred }
在編輯執行我們前面加的命令即可
npm run testMyPromise

完善其他方法
- all
- allSettled
- any
- race
- catch
- finlly
npm run dev // 可以用來測試這些方法

源碼
比較官方的源碼: https://github.com/then/promise
參考
https://www.jianshu.com/p/4d266538f364
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
