2021-05-12 14:32:11
簡單版 Promise/A+,通過官方872個測試用例
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://www.jianshu.com/p/4d266538f364
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
相關文章