前言
[實踐系列] 主要是讓我們通過實踐去加深對一些原理的理解。
實踐系列-前端路由
實踐系列-Babel原理
有興趣的同學可以關注 實踐系列 。 求star求follow~
什么是Promise ?
Promise是JS異步編程中的重要概念,異步抽象處理對象,是目前比較流行Javascript異步編程解決方案之一
Promises/A+ 規范
為實現者提供一個健全的、可互操作的 JavaScript promise 的開放標準。
術語
- 解決 (fulfill) : 指一個 promise 成功時進行的一系列操作,如狀態的改變、回調的執行。雖然規范中用 fulfill 來表示解決,但在后世的 promise 實現多以 resolve 來指代之。
- 拒絕(reject) : 指一個 promise 失敗時進行的一系列操作。
- 拒因 (reason) : 也就是拒絕原因,指在 promise 被拒絕時傳遞給拒絕回調的值。
- 終值(eventual value) : 所謂終值,指的是 promise 被解決時傳遞給解決回調的值,由于 promise 有一次性的特征,因此當這個值被傳遞時,標志著 promise 等待態的結束,故稱之終值,有時也直接簡稱為值(value)。
- Promise : promise 是一個擁有 then 方法的對象或函數,其行為符合本規范。
- thenable : 是一個定義了 then 方法的對象或函數,文中譯作“擁有 then 方法”。
- 異常(exception) : 是使用 throw 語句拋出的一個值。
基本要求
下面我們先來講述Promise/A+ 規范的幾個基本要求。
1. Promise的狀態
一個Promise的當前狀態必須是以下三種狀態中的一種: 等待狀態(Pending) 執行狀態(Fulfilled) 和 拒絕狀態(Rejected)。
const PENDING = 'pending';const FULFILLED = 'fulfilled';const REJECTED = 'rejected';
等待狀態 (Pending)
處于等待態時,promise 需滿足以下條件:
- 可以遷移至執行態或拒絕態
if (this.state === PENDING) {this.state = FULFILLED || REJECTED ;}
執行狀態 (Fulfilled)
處于執行態時,promise 需滿足以下條件:
- 不能遷移至其他任何狀態
- 必須擁有一個不可變的終值
this.value = value;
拒絕狀態 (Rejected)
處于拒絕態時,promise 需滿足以下條件:
- 不能遷移至其他任何狀態
- 必須擁有一個不可變的據因
this.reason = reason;
這里的不可變指的是恒等(即可用 === 判斷相等),而不是意味著更深層次的不可變(譯者注:蓋指當 value 或 reason 不是基本值時,只要求其引用地址相等,但屬性值可被更改)
2. Then 方法
一個 promise 必須提供一個 then 方法以訪問其當前值、終值和據因。
promise 的 then 方法接受兩個參數:
promise.then(onFulfilled, onRejected)
參數可選
onFulfilled 和 onRejected 都是可選參數。
- 如果 onFulfilled 不是函數,其必須被忽略
- 如果 onRejected 不是函數,其必須被忽略
onFulfilled 特性
如果 onFulfilled 是函數:
- 當 promise 執行結束后其必須被調用,其第一個參數為 promise 的終值
- 在 promise 執行結束前其不可被調用
- 其調用次數不可超過一次
onRejected 特性
如果 onRejected 是函數:
- 當 promise 被拒絕執行后其必須被調用,其第一個參數為 promise 的據因
- 在 promise 被拒絕執行前其不可被調用
- 其調用次數不可超過一次
調用時機
onFulfilled 和 onRejected 只有在執行環境堆棧僅包含平臺代碼時才可被調用 注1
注1 這里的平臺代碼指的是引擎、環境以及 promise 的實施代碼。實踐中要確保 onFulfilled 和 onRejected 方法異步執行,且應該在 then 方法被調用的那一輪事件循環之后的新執行棧中執行。
這個事件隊列可以采用“宏任務(macro - task)”機制或者“微任務(micro - task)”機制來實現。
由于 promise 的實施代碼本身就是平臺代碼(譯者注:即都是 JavaScript),故代碼自身在處理在處理程序時可能已經包含一個任務調度隊列。
調用要求
onFulfilled 和 onRejected 必須被作為函數調用(即沒有 this 值)
多次調用
then 方法可以被同一個 promise 調用多次
- 當 promise 成功執行時,所有 onFulfilled 需按照其注冊順序依次回調
- 當 promise 被拒絕執行時,所有的 onRejected 需按照其注冊順序依次回調
簡易版實踐
我們先通過實踐一個簡易版的Promise來消化一下上面Promises/A+規范的基本要求。
首先
npm init // 測試實現是否符合 promises/A+ 規范npm install promises-aplus-tests -D
package.json
{"name": "ajpromise","version": "1.0.0","description": "","main": "index.js","scripts": {"test": "promises-aplus-tests ./simple.js"},"author": "webfansplz","license": "MIT","devDependencies": {"promises-aplus-tests": "^2.1.2"}
}
simple.js
//Promise 的三種狀態 (滿足要求 -> Promise的狀態)
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';class AjPromise {constructor(fn) {//當前狀態this.state = PENDING;//終值this.value = null;//拒因this.reason = null;//成功態回調隊列this.onFulfilledCallbacks = [];//拒絕態回調隊列this.onRejectedCallbacks = [];//成功態回調const resolve = value => {// 使用macro-task機制(setTimeout),確保onFulfilled異步執行,且在 then 方法被調用的那一輪事件循環之后的新執行棧中執行。setTimeout(() => {if (this.state === PENDING) {// pending(等待態)遷移至 fulfilled(執行態),保證調用次數不超過一次。this.state = FULFILLED;// 終值this.value = value;this.onFulfilledCallbacks.map(cb => {this.value = cb(this.value);});}});};//拒絕態回調const reject = reason => {// 使用macro-task機制(setTimeout),確保onRejected異步執行,且在 then 方法被調用的那一輪事件循環之后的新執行棧中執行。 (滿足要求 -> 調用時機)setTimeout(() => {if (this.state === PENDING) {// pending(等待態)遷移至 fulfilled(拒絕態),保證調用次數不超過一次。this.state = REJECTED;//拒因this.reason = reason;this.onRejectedCallbacks.map(cb => {this.reason = cb(this.reason);});}});};try {//執行promisefn(resolve, reject);} catch (e) {reject(e);}}then(onFulfilled, onRejected) {typeof onFulfilled === 'function' && this.onFulfilledCallbacks.push(onFulfilled);typeof onRejected === 'function' && this.onRejectedCallbacks.push(onRejected);// 返回this支持then 方法可以被同一個 promise 調用多次return this;}
}
就這樣,一個簡單的promise就完成了.
new AjPromise((resolve, reject) => {setTimeout(() => {resolve(2);}, 2000);
}).then(res => {console.log(res);return res + 1;}).then(res => {console.log(res);});//output // delay 2s..
// 2
// 3
接下來,我們來看看我們的實現是否完全符合promises/A+規范~
npm run test
GG,測試用例只過了一小部分,大部分飄紅~
OK,接下來,我們來繼續了解promises/A+ 進一步的規范要求~
進一步要求
由于接下來的要求比較抽象和難理解,所以我們將一步一步實踐來加深理解。
1. 返回
- 1.then方法必須返回一個promise對象
- 2.如果 onFulfilled 或者 onRejected 返回一個值 x ,則運行下面的 Promise 解決過程:[[Resolve]](promise2, x)
- 3.如果 onFulfilled 或者 onRejected 拋出一個異常 e ,則 promise2 必須拒絕執行,并返回拒因 e。
- 4.如果 onFulfilled 不是函數且 promise1 成功執行, promise2 必須成功執行并返回相同的值。
- 5.如果 onRejected 不是函數且 promise1 拒絕執行, promise2 必須拒絕執行并返回相同的據因。
- 6.不論 promise1 被 reject 還是被 resolve 時 promise2 都會被 resolve,只有出現異常時才會被 rejected。
我們通過以上要求來一步一步完善then方法
1.
// 1.首先,then方法必須返回一個promise對象then(onFulfilled, onRejected) {let newPromise;return (newPromise = new AjPromise((resolve, reject) => {}));}
2.
then(onFulfilled, onRejected) {let newPromise;return (newPromise = new AjPromise((resolve, reject) => {// 2.如果 onFulfilled 或者 onRejected 返回一個值 x ,則運行下面的 Promise 解決過程:[[Resolve]](promise2, x)this.onFulfilledCallbacks.push(value => {let x = onFulfilled(value);//解決過程 resolvePromiseresolvePromise(newPromise, x);});this.onRejectedCallbacks.push(reason => {let x = onRejected(reason);//解決過程 resolvePromiseresolvePromise(newPromise, x);});}));}// 解決過程function resolvePromise() {//...}
3.
then(onFulfilled, onRejected) {let newPromise;return (newPromise = new AjPromise((resolve, reject) => {// 3.如果 onFulfilled 或者 onRejected 拋出一個異常 e ,則 promise2 必須拒絕執行,并返回拒因 e。this.onFulfilledCallbacks.push(value => {try {let x = onFulfilled(value);resolvePromise(newPromise, x);} catch (e) {reject(e);}});this.onRejectedCallbacks.push(reason => {try {let x = onRejected(reason);resolvePromise(newPromise, x);} catch (e) {reject(e);}});}));}
4,5.
then(onFulfilled, onRejected) { let newPromise;// 4.如果 onFulfilled 不是函數且 promise1 成功執行, promise2 必須成功執行并返回相同的值。onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;// 5.如果 onRejected 不是函數且 promise1 拒絕執行, promise2 必須拒絕執行并返回相同的據因。onRejected =typeof onRejected === 'function'? onRejected: reason => {throw reason;};return (newPromise = new AjPromise((resolve, reject) => {this.onFulfilledCallbacks.push(value => {try {let x = onFulfilled(value);resolvePromise(newPromise, x);} catch (e) {reject(e);}});this.onRejectedCallbacks.push(reason => {try {let x = onRejected(reason);resolvePromise(newPromise, x);} catch (e) {reject(e);}});}));}
6.
then(onFulfilled, onRejected) {let newPromise;onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;onRejected =typeof onRejected === 'function'? onRejected: reason => {throw reason;};// 2.2.6規范 對于一個promise,它的then方法可以調用多次.// 當在其他程序中多次調用同一個promise的then時 由于之前狀態已經為FULFILLED / REJECTED狀態,則會走以下邏輯,// 所以要確保為FULFILLED / REJECTED狀態后 也要異步執行onFulfilled / onRejected ,這里使用setTimeout// 6.不論 promise1 被 reject 還是被 resolve 時 promise2 都會被 resolve,只有出現異常時才會被 rejected。// 由于在接下來的解決過程中需要調用resolve,reject進行處理,處理我們在調用處理過程時,傳入參數if (this.state == FULFILLED) { return (newPromise = new AjPromise((resolve, reject) => {setTimeout(() => {try {let x = onFulfilled(this.value);resolvePromise(newPromise, x, resolve, reject);} catch (e) {reject(e);}});}));}if (this.state == REJECTED) {return (newPromise = new AjPromise((resolve, reject) => {setTimeout(() => {try {let x = onRejected(this.reason);resolvePromise(newPromise, x, resolve, reject);} catch (e) {reject(e);}});}));}if (this.state === PENDING) {return (newPromise = new AjPromise((resolve, reject) => {this.onFulfilledCallbacks.push(value => {try {let x = onFulfilled(value);resolvePromise(newPromise, x, resolve, reject);} catch (e) {reject(e);}});this.onRejectedCallbacks.push(reason => {try {let x = onRejected(reason);resolvePromise(newPromise, x, resolve, reject);} catch (e) {reject(e);}});}));}}
ok,完整的then方法搞定了。相信通過以上實踐,你對返回要求已經有了更深的理解。
2. Promise 解決過程
Promise 解決過程是一個抽象的操作,其需輸入一個 promise 和一個值,我們表示為 [[Resolve]](promise, x),如果 x 有 then 方法且看上去像一個 Promise ,解決程序即嘗試使 promise 接受 x 的狀態;否則其用 x 的值來執行 promise 。這種 thenable 的特性使得 Promise 的實現更具有通用性:只要其暴露出一個遵循 Promise/A+ 協議的 then 方法即可;這同時也使遵循 Promise/A+ 規范的實現可以與那些不太規范但可用的實現能良好共存。
運行 [[Resolve]](promise, x) 需遵循以下步驟:
1。x 與 promise 相等
如果 promise 和 x 指向同一對象,以 TypeError 為據因拒絕執行 promise。
2。x 為 Promise
- 如果 x 為 Promise ,則使 promise 接受 x 的狀態。
- 如果 x 處于等待態, promise 需保持為等待態直至 x 被執行或拒絕。
- 如果 x 處于執行態,用相同的值執行 promise。
- 如果 x 處于拒絕態,用相同的據因拒絕 promise。
3。x 為對象或函數
如果 x 為對象或者函數:
- 把 x.then 賦值給 then。
- 如果取 x.then 的值時拋出錯誤 e ,則以 e 為據因拒絕 promise。
-
如果 then 是函數,將 x 作為函數的作用域 this 調用之。傳遞兩個回調函數作為參數,第一個參數叫做 resolvePromise ,第二個參數叫做 rejectPromise:
- 如果 resolvePromise 以值 y 為參數被調用,則運行 [[Resolve]](promise, y)
- 如果 rejectPromise 以據因 r 為參數被調用,則以據因 r 拒絕 promise
- 如果 resolvePromise 和 rejectPromise 均被調用,或者被同一參數調用了多次,則優先采用首次調用并忽略剩下的調用
-
如果調用 then 方法拋出了異常 e:
- 如果 resolvePromise 或 rejectPromise 已經被調用,則忽略之
- 否則以 e 為據因拒絕 promise
- 如果 then 不是函數,以 x 為參數執行 promise
- 如果 x 不為對象或者函數,以 x 為參數執行 promise
如果一個 promise 被一個循環的 thenable 鏈中的對象解決,而 [[Resolve]](promise, thenable) 的遞歸性質又使得其被再次調用,根據上述的算法將會陷入無限遞歸之中。算法雖不強制要求,但也鼓勵施者檢測這樣的遞歸是否存在,若檢測到存在則以一個可識別的 TypeError 為據因來拒絕 promise 。
1.x 與 promise 相等
function resolvePromise(promise2, x, resolve, reject) {//x 與 promise 相等 //如果從onFulfilled中返回的x 就是promise2 就會導致循環引用報錯//如果 promise 和 x 指向同一對象,以 TypeError 為據因拒絕執行 promiseif (x === promise2) {reject(new TypeError('循環引用'));}
}
2.x 為 Promise。
function resolvePromise(promise2, x, resolve, reject) {if (x === promise2) {reject(new TypeError('循環引用'));}// x 為 Promiseelse if (x instanceof AjPromise) {// 如果 x 為 Promise ,則使 promise 接受 x 的狀態// 如果 x 處于等待態, promise 需保持為等待態直至 x 被執行或拒絕if (x.state === PENDING) {x.then(y => {resolvePromise(promise2, y, resolve, reject);},reason => {reject(reason);});} else {// 如果 x 處于執行態,用相同的值執行 promise// 如果 x 處于拒絕態,用相同的據因拒絕 promisex.then(resolve, reject);}}
}
3.x 為對象或函數
function resolvePromise(promise2, x, resolve, reject) {if (x === promise2) {reject(new TypeError('循環引用'));}if (x instanceof AjPromise) {if (x.state === PENDING) {x.then(y => {resolvePromise(promise2, y, resolve, reject);},reason => {reject(reason);});} else {x.then(resolve, reject);}} else if (x && (typeof x === 'function' || typeof x === 'object')) {// 避免多次調用let called = false;try {//把 x.then 賦值給 thenlet then = x.then;if (typeof then === 'function') {// 如果 then 是函數,將 x 作為函數的作用域 this 調用之。// 傳遞兩個回調函數作為參數,第一個參數叫做 resolvePromise ,第二個參數叫做 rejectPromise// 如果 resolvePromise 和 rejectPromise 均被調用,或者被同一參數調用了多次,則優先采用首次調用并忽略剩下的調用then.call(x,// 如果 resolvePromise 以值 y 為參數被調用,則運行[[Resolve]](promise, y)y => {if (called) return;called = true;resolvePromise(promise2, y, resolve, reject);},// 如果 rejectPromise 以據因 r 為參數被調用,則以據因 r 拒絕 promiser => {if (called) return;called = true;reject(r);});}else {// 如果 then 不是函數,以 x 為參數執行 promiseresolve(x);} } catch (e) {// 如果取 x.then 的值時拋出錯誤 e ,則以 e 為據因拒絕 promise// 如果調用 then 方法拋出了異常 e:// 如果 resolvePromise 或 rejectPromise 已經被調用,則忽略之// 否則以 e 為據因拒絕 promiseif (called) return;called = true;reject(e);}} else {// 如果 x 不為對象或者函數,以 x 為參數執行 promiseresolve(x);}
}
Ok~比較復雜的解決過程也讓我們搞定了.接下來我們整合下代碼
Promises/A+ 規范 實踐
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';class AjPromise {constructor(fn) {this.state = PENDING;this.value = null;this.reason = null;this.onFulfilledCallbacks = [];this.onRejectedCallbacks = [];const resolve = value => {if (value instanceof Promise) {return value.then(resolve, reject);}setTimeout(() => {if (this.state === PENDING) {this.state = FULFILLED;this.value = value;this.onFulfilledCallbacks.map(cb => {cb = cb(this.value);});}});};const reject = reason => {setTimeout(() => {if (this.state === PENDING) {this.state = REJECTED;this.reason = reason;this.onRejectedCallbacks.map(cb => {cb = cb(this.reason);});}});};try {fn(resolve, reject);} catch (e) {reject(e);}}then(onFulfilled, onRejected) {let newPromise;onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;onRejected =typeof onRejected === 'function'? onRejected: reason => {throw reason;};if (this.state === FULFILLED) {return (newPromise = new AjPromise((resolve, reject) => {setTimeout(() => {try {let x = onFulfilled(this.value);resolvePromise(newPromise, x, resolve, reject);} catch (e) {reject(e);}});}));}if (this.state === REJECTED) {return (newPromise = new AjPromise((resolve, reject) => {setTimeout(() => {try {let x = onRejected(this.reason);resolvePromise(newPromise, x, resolve, reject);} catch (e) {reject(e);}});}));}if (this.state === PENDING) {return (newPromise = new AjPromise((resolve, reject) => {this.onFulfilledCallbacks.push(value => {try {let x = onFulfilled(value);resolvePromise(newPromise, x, resolve, reject);} catch (e) {reject(e);}});this.onRejectedCallbacks.push(reason => {try {let x = onRejected(reason);resolvePromise(newPromise, x, resolve, reject);} catch (e) {reject(e);}});}));}}
}
function resolvePromise(promise2, x, resolve, reject) {if (x === promise2) {reject(new TypeError('循環引用'));}if (x instanceof AjPromise) {if (x.state === PENDING) {x.then(y => {resolvePromise(promise2, y, resolve, reject);},reason => {reject(reason);});} else {x.then(resolve, reject);}} else if (x && (typeof x === 'function' || typeof x === 'object')) {let called = false;try {let then = x.then;if (typeof then === 'function') {then.call(x,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 {resolve(x);}
}AjPromise.deferred = function() {let defer = {};defer.promise = new AjPromise((resolve, reject) => {defer.resolve = resolve;defer.reject = reject;});return defer;
};module.exports = AjPromise;
再來看看我們的實現是否符合Promises/A+規范
npm run test
nice,測試用例全部通過!
源碼地址
傳送門
如果覺得有幫助到你,請給個star支持下作者~
參考文獻
Promises/A+規范譯文
Promise詳解與實現