promise/A+規範翻譯以及手寫實現
如果要手寫實現promise,那麼先看看promise/A+規範,再來實現,將會事半功倍。
那麼我先翻譯一下 Promise/A+規範 中的內容。
- 術語
1.1 promise 是一個帶有符合此規範的then方法的物件或者函式。
1.2 thenable 是一個定義了一個then方法的物件或者函式。
1.3 value 是一個任意合法的JavaScript值(包括undefined, thenable,或者promise)。
1.4 exception 是一個使用throw語句丟擲的值。
1.5 reason 是一個指出為什麼promise被rejected的值。 -
要求
2.1 promise 狀態
一個promise必須是三種狀態其中的一種狀態:pending,fulfilled 或者 rejected。
2.1.1 當promise處於pending狀態時:
2.1.1.1 可以轉變到 fulfilled 或者 rejected 狀態。
2.1.2 當promise處於fulfilled狀態時:
2.1.2.1 一定不能夠轉變到其他任何一種狀態。 2.1.2.2 必須有一個value,並且這個值一定不能改變。
2.1.3 當promise處於rejected狀態時:
2.1.3.1 一定不能夠轉變到其他任何一種狀態。 2.1.3.2 必須有一個reason,並且這個值不能夠改變。
在這裡,“一定不能改變”意味著不可變的身份(即===),但並不意味著深層不變性。(個人理解為是value/reason指向的地址是不可變的,但假若value/reason為一個物件,則物件內的值是可變的。)
2.2 then 方法
一個promise必須提供一個then方法去訪問當前或者最終的value或者reason。
一個promise的then方法接受兩個引數:
promise.then(onFulfilled, onRejected)
2.2.1 onFulfilled 和 onRejected 都是可選的引數:
2.2.1.1 如果 onFulfilled不是一個函式,它必須被忽略。 2.2.1.2 如果 onRejected 不是一個函式,它必須被忽略。
2.2.2 如果 onFulfilled 是一個函式:
2.2.2.1 promise 是 fulfilled 狀態時它必須被呼叫,並且 promise 的 value 作為它的第一個引數。 2.2.2.2 它一定不能在 promise 進入 fulfilled 狀態之前被呼叫。 2.2.2.3 它一定不能夠呼叫超過一次。
2.2.3 如果 onRejected 時一個函式:
2.2.3.1 promise 是 rejected 狀態時它必須被呼叫,並且 promise 的 reason 作為它的第一個引數。 2.2.3.2 它一定不能在 promise 進入 rejected 狀態之前被呼叫。 2.2.3.3 它一定不能夠呼叫超過一次。
2.2.4 直到執行上下文堆疊僅包含平臺程式碼之前,onFulfilled 或 onRejected 不能夠被呼叫。[3.1]
2.2.5 onFulfilled 和 onRejected 必須以函式的形式被呼叫(即沒有this值)。[3.2]
2.2.6 then 可以在同一個promise被呼叫多次:
2.2.6.1 當 promise 處於 fulfilled 狀態時,各個 onFulfilled 回撥必須按其原始呼叫的順序執行。 2.2.6.2 當 promise 處於 rejected 狀態時,各個 onRejected 回撥必須按其原始呼叫的順序執行。
2.2.7 then 必須返回一個promise [3.3]:
promise2 = promise1.then(onFulfilled, onRejected);
2.2.7.1 如果 onFulfilled 或 onRejected 返回一個值 x,執行Promise解決程式 [[Resolve]](promise2, x)。 2.2.7.2 如果 onFulfilled 或 onRejected 丟擲一個意外 e,promise2 必須以 e 為 reason 被 rejected。 2.2.7.3 如果 onFulfilled 不是一個函式並且 promise1 處於 fulfilled 狀態,promise2 必須以與 promise1 同樣的 value 轉變到 fulfilled 狀態。 2.2.7.4 如果 onRejected 不是一個函式並且promise1 處於 rejected狀態,promise2 必須以與 promise1 同樣的 reason 轉變到 rejected狀態。
2.3 Promise解決程式
promise解決程式是一個抽象的操作,它把一個 promise 和一個 value 作為輸入,我們將這個表示為 [[Resolve]](promise, x)。如果 x 是一個 thenable ,它將會試圖讓 promise 採用 x 的狀態,前提是x的行為至少有點像一個 promise。否則,它將會用值 x 執行 promise。
對這些 thenable 的處理使得與 promise 實現方式能夠去互相操作。只要它們公開了符合 Promise/A+ 的 then 方法。它還使得 promises/A+ 實現方式能夠採用合理的 then 方法去“同化”不一致的實現方式。
為了執行[[Resolve]](promise, x),執行以下步驟:
2.3.1 如果 promise 與 x 是同一個物件,以 Tyeperror 作為 reason 去 reject promise。
2.3.2 如果 x 是一個 promise,使用它的狀態:
2.3.2.1 如果 x 處於 pending 狀態,promise 必須保持 pending 狀態直到 x 處於 fulfilled 或者 rejected 狀態。 2.3.2.2 如果 x 處於 fulfilled 狀態,以相同的 value 去 fulfill promise。 2.3.2.3 如果 x 處於 rejected 狀態,以相同的 reason 去 reject promise。
2.3.3 否則,如果 x 是一個物件或者函式:
2.3.3.1 讓 then 作為 x.then。 2.3.3.2 如果取屬性 x.then 會導致丟擲異常 e,則以 e 為 reason reject promise。 2.3.3.3 如果 then 是一個函式,讓 x 作為 this 呼叫它,第一個引數為 resolvePromise,第二個引數為 rejectPromise,然後: 2.3.3.3.1 如果使用value y 呼叫 resolvepromise 時,執行[[Resolve]](promise, y)。 2.3.3.3.2 如果使用reason r 呼叫 rejectPromise 時,也用 r reject promise。 2.3.3.3.3 如果 resolvePromise 和 rejectPromise 都被呼叫了,或多次呼叫同一引數,那麼第一個呼叫優先,其他的呼叫都會被忽略。 2.3.3.3.4 如果呼叫 then 的過程中丟擲了一個意外 e, 2.3.3.3.4.1 如果 resolvePromise 或者 rejectPromise 被呼叫了,那麼忽略它。 2.3.3.3.4.2 否則,把 e 作為 reason reject promise。 2.3.3.4 如果 then 不是一個函式,將 x 作為引數執行 promise。
2.3.4 如果 x 不是一個物件或者函式,將 x 作為引數執行 promise。
如果一個參與了 thenable 迴圈鏈的 thenable 去 resolve promise,這樣 [[Resolve]](promise, thenable) 的遞迴性質最終會導致 [[Resolve]](promise, thenable) 會被再次呼叫,遵循上述演算法將會導致無限遞迴。我們鼓勵去實現(但不是必需的)檢測這樣的遞迴,並以 TypeError 作為 reason 去 reject Promise。[3.6]
- 注意
3.1 這裡的“平臺程式碼”指的是引擎,環境和 promise 實現程式碼。實際上,這個要求保證了 onFulfilled 和 onRejected 將會非同步執行,在事件迴圈之後,用一個新的堆疊來呼叫它。 這可以通過“巨集任務”機制(如 settimeou t或 setimmediate )或“微任務”機制(如 mutationobserver 或 process.nextick)來實現。由於 Promise 實現被視為平臺程式碼,因此它本身可能包含一個任務排程佇列或“trampoline”,並在其中呼叫處理程式。
3.2 也就是說,在 strict 模式下,這(指的是this)在它們內部將會是 undefined;在 sloppy 模式下,它將會是全域性物件。
3.3 如果實現滿足所有要求,則實現可能允許 promise2 == promise1。每個實現都應該記錄它是否能夠生成 promise2 == promise1 以及在什麼條件下。
3.4 一般來說,只有當 X 來自當前的實現時,才知道它是一個真正的 promise。本條款允許使用特定於實現的方法來採用已知一致承諾的狀態。
3.5 此過程首先儲存對 x 的引用,然後測試該引用,然後呼叫該引用,避免多次訪問 x.then 屬性。這些預防措施對於確保訪問器屬性的一致性非常重要,訪問器屬性的值可能在兩次檢索之間發生更改。
3.6 實現方式中不應當在 thenbale 鏈中的深度設定主觀的限制,並且不應當假設鏈的深度超過主觀的限制後會是無限的。只有真正的迴圈才能導致 TypeError。如果遇到由無限多個不同 thenable 組成的鏈,那麼永遠遞迴是正確的行為。
接下來根據規範進行手寫實現,註釋偷懶就將對應的規範標註出來,其實基本上就是對著規範實現。
function promise(fuc){ //接收一個函式作為引數 let that = this; that.value = null; // 2.1.2.2 that.reason = null;// 2.1.3.2 that.onFulfilled = []; // 2.2.6 that.onRejected = []; //2.2.6 that.status = 'pending'; // 2.1 function resolve(val){ if (that.status === 'pending') { // 2.1.2 that.status = 'fulfilled'; that.value = val; that.onFulfilled.forEach(fc => fc(val)); //2.2.6.1 } } function reject(err){ if (that.status === 'pending') { //2.1.3 that.status = 'rejected'; that.reason = err; that.onRejected.forEach(fc => fc(err)); //2.2.6.2 } } try { fuc(resolve,reject); } catch (error) { reject(error); } } promise.prototype.then = function (onFulfilled, onRejected) //2.2 { let that = this, promise2; //2.2.7 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (x) => x; //2.2.1 2.2.7.3 onRejected = typeof onRejected === 'function' ? onRejected : (e) => { throw e }; // 2.2.7.4 switch (that.status) { case 'pending': promise2 = new promise((reslove, reject)=>{ that.onFulfilled.push(()=>{ setTimeout(() => { try { let x = onFulfilled(that.value); //2.2.2.1 ResolutionProcedure(promise2, x, reslove, reject); //2.2.7.1 } catch (err) { reject(err); //2.2.7.2 } }, 0); }); that.onRejected.push(()=>{ setTimeout(() => { try { let x = onRejected(that.reason); //2.2.3.1 ResolutionProcedure(promise2, x, reslove, reject); //2.2.7.1 } catch (err) { reject(err); //2.2.7.2 } }, 0); }); }) break; case 'fulfilled': promise2 = new promise((reslove,reject)=>{ setTimeout(() => { // 2.2.4 try{ let x = onFulfilled(that.value); //2.2.2.1 ResolutionProcedure(promise2, x, reslove, reject); //2.2.7.1 }catch(err){ reject(err); //2.2.7.2 } }, 0); }) break; case 'rejected': promise2 = new promise((reslove, reject)=>{ setTimeout(() => { // 2.2.4 try{ let x = onRejected(that.reason); //2.2.3.1 ResolutionProcedure(promise2, x, reslove, reject); //2.2.7.1 }catch(err){ reject(err); //2.2.7.2 } }, 0); }) break; default: break; } return promise2; } function ResolutionProcedure(promise, x, reslove, reject){ //2.3 if (promise === x) //2.3.1 return reject(new TypeError("same promise")); if (x instanceof Promise) //2.3.2 x.then(((xvalue)=>{ ResolutionProcedure(promise, xvalue, reslove, reject); },(xreason)=>{ reject(xreason) })); if (x !== null &&( typeof x === 'object' || typeof x === 'function' )) //2.3.3 { try { let then = x.then;//2.3.3.1 if(typeof then === 'function') //2.3.3.3 { let isuse = false; try { then.call(x,(y)=>{ if (!isuse) { ResolutionProcedure(promise, y, reslove, reject); //2.3.3.3.1 isuse = true; } },(r)=>{ if (!isuse) { reject(r) //2.3.3.3.2 isuse = true; } }) } catch (error) { if(!isuse) reject(error) //2.3.3.3.4.2 } }else{ reslove(x); //2.3.3.4 } } catch (error) { reject(error); //2.3.3.2 } }else{ reslove(x); //2.3.4 } }
做完之後可以通過 promise test 進行測試。
先在程式碼下面加上一些測試需要程式碼。
promise.deferred = function () { let def = {}; def.promise = new promise(function (resolve, reject) { def.resolve = resolve; def.reject = reject; }); return def; } module.exports = promise
然後跑一下下面的程式碼即可。
npm install -g promises-aplus-tests promises-aplus-testspromise.js
結果全部通過,說明符合規範: