最近看了 Promise/A+ 的規範,嘗試實現了一個知足 promises-aplus-tests 測試的 Promise 類,在實現規範的過程當中,對於 Promise 自己也加深了理解,這篇文章就將個人實現過程分享出來。git
pending
,fulfilled
和 rejected
,狀態只能從 pending
轉移到 fulfilled
或者 rejected
,一旦狀態變成fulfilled
或者 rejected
,就不能再更改其狀態。2.1.1 When pending, a promise: 2.1.1.1 may transition to either the fulfilled or rejected state. 2.1.2 When fulfilled, a promise: 2.1.2.1 must not transition to any other state. 2.1.2.2 must have a value, which must not change. 2.1.3 When rejected, a promise: 2.1.3.1 must not transition to any other state. 2.1.3.2 must have a reason, which must not change.github
thenable
對象是一類具備 then
方法的對象或者函數。1.2 「thenable」 is an object or function that defines a then method.npm
value
值,這個 value
值能夠是任意合法的 JavaScript 數據類型。1.3 「value」 is any legal JavaScript value (including undefined, a thenable, or a promise).promise
value
屬性,Promise 內部還有一個 reason
屬性,用來存放 Promise 狀態變爲 rejected
的緣由1.5 「reason」 is a value that indicates why a promise was rejected.瀏覽器
根據上面的介紹,能夠初步構造一個 MyPromise
類:bash
class MyPromise {
constructor(exector) {
this.status = MyPromise.PENDING;
this.value = null;
this.reason = null;
this.initBind();
this.init(exector);
}
initBind() {
// 綁定 this
// 由於 resolve 和 reject 會在 exector 做用域中執行,所以這裏須要將 this 綁定到當前的實例
this.resolve = this.resolve.bind(this);
this.reject = this.reject.bind(this);
}
init(exector) {
try {
exector(this.resolve, this.reject);
} catch (err) {
this.reject(err);
}
}
resolve(value) {
if (this.status === MyPromise.PENDING) {
this.status = MyPromise.FULFILLED;
this.value = value;
}
}
reject(reason) {
if (this.status === MyPromise.PENDING) {
this.status = MyPromise.REJECTED;
this.reason = reason;
}
}
}
// 2.1 A promise must be in one of three states: pending, fulfilled, or rejected.
MyPromise.PENDING = "pending"
MyPromise.FULFILLED = "fulfilled"
MyPromise.REJECTED = "rejected"
複製代碼
exector
是建立 Promise 對象時傳遞給構造函數的參數,resolve
和 reject
方法分別用來將 Promise 對象的狀態由 pending
轉換成 fulfilled
和 rejected
,並向 Promise 對象中寫入相應的 value
或者 reason
值。 如今,咱們能夠對上面的代碼進行一些測試:frontend
const p1 = new MyPromise((resolve,reject) => {
const rand = Math.random();
if(rand > 0.5) resolve(rand)
else reject(rand)
})
console.log(p1)
// MyPromise {status: "fulfilled", value: 0.9121690746412516, reason: null, resolve: ƒ, reject: ƒ}
複製代碼
上面的代碼,已經可讓 Promise 對象實現狀態變換,並保存 value
或者 reason
值,但單純完成狀態的轉換和保存值是不夠的,做爲異步的解決方案,咱們還須要讓 Promise 對象在狀態變換後再作點什麼。 這就須要咱們爲 Promise 對象再提供一個 then
方法。dom
A promise must provide a then method to access its current or eventual value or reason.異步
then
方法接受兩個參數:Promise 狀態轉換爲 fulfilled
的回調(成功回調)和狀態轉換爲 rejected
的回調(失敗回調),這兩個回調函數是可選的。async
A promise’s then method accepts two arguments: promise.then(onFulfilled, onRejected) 2.2.1 Both onFulfilled and onRejected are optional arguments: 2.2.1.1 If onFulfilled is not a function, it must be ignored. 2.2.1.2 If onRejected is not a function, it must be ignored.
下面爲 MyPromise 類添加一個 then
方法:
...
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : () => {}
onRejected = typeof onRejected === "function" ? onRejected : () => {}
if (this.status === MyPromise.FULFILLED) {
try{
onFulfilled(this.value)
}catch(e){
onRejected(e)
}
}
if (this.status === MyPromise.REJECTED) {
try{
onRejected(this.reason);
}catch(e){
onRejected(e)
}
}
}
...
複製代碼
下面測試一下 then
方法:
const p1 = new MyPromise((resolve) => resolve("Success"))
p1.then(data => console.log(data))
// Success
複製代碼
這裏,咱們初步完成了 MyPromise 類的 then
方法。 但仔細看上面的 then
方法和 MyPromise 類的實現,還存在幾個缺陷:
fulfilled
和 rejected
的狀況,沒有處理狀態爲 pending
的狀況onFulfilled
和 onRejected
方法是同步執行的,也就是說,調用 then
方法,就會執行 onFulfilled
和 onRejected
方法resolve
和 reject
方法也是同步的,這意味着會出現下面的狀況:console.log("START")
const p2 = new MyPromise(resolve => resolve("RESOLVED"))
console.log(p2.value)
console.log("END")
複製代碼
輸出結果爲:
START
RESOLVED
END
複製代碼
按照規範,Promise 應該是異步的。
2.2.4
onFulfilled
oronRejected
must not be called until the execution context stack contains only platform code.
規範還指出了,應該使用 setTimeout
或 setImmediate
這樣的宏任務方式,或者 MutationObserver
或 process.nextTick
這樣的微任務方式,來調用 onFulfilled
和 onRejected
方法。
Here 「platform code」 means engine, environment, and promise implementation code. In practice, this requirement ensures that
onFulfilled
andonRejected
execute asynchronously, after the event loop turn in whichthen
is called, and with a fresh stack. This can be implemented with either a 「macro-task」 mechanism such assetTimeout
orsetImmediate
, or with a 「micro-task」 mechanism such asMutationObserver
orprocess.nextTick
. Since the promise implementation is considered platform code, it may itself contain a task-scheduling queue or 「trampoline」 in which the handlers are called.
exector
方法爲異步的狀況:const p3 = new MyPromise(resolve => setTimeout(() => resolve("RESOLVED")));
p3.then(data => console.log(data))
// 無輸出
複製代碼
這裏無輸出的緣由是在實現 then
方法的時候,沒有處理狀態爲 pending
的狀況,那麼在 pending
狀態下,對於 then
方法的調用,不會有任何的響應,所以在 then
方法中,對於 pending
狀態的處理也很重要。 下面就針對上面出現的問題,作一些改進。
首先,應該確保 onFulfilled
和 onRejected
方法,以及 resolve
和 reject
方法是異步調用的:
...
resolve(value) {
if (this.status === MyPromise.PENDING) {
setTimeout(() => {
this.status = MyPromise.FULFILLED;
this.value = value;
this.onFulfilledCallback.forEach(cb => cb(this.value));
})
}
}
reject(reason) {
if (this.status === MyPromise.PENDING) {
setTimeout(() => {
this.status = MyPromise.REJECTED;
this.reason = reason;
this.onRejectedCallback.forEach(cb => cb(this.reason));
})
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : () => {}
onRejected = typeof onRejected === "function" ? onRejected : () => {}
if (this.status === MyPromise.FULFILLED) {
setTimeout(() => {
try{
onFulfilled(this.value)
}catch(e){
onRejected(e)
}
})
}
if (this.status === MyPromise.REJECTED) {
setTimeout(() => {
try{
onRejected(this.reason);
}catch(e){
onRejected(e)
}
})
}
}
...
複製代碼
而後,須要在 MyPromise 類中,在設置兩個隊列:onFulfilledCallback
,和 onRejectedCallback
,用來存放在 pending
狀態下,調用 then
方法時傳入的回調函數。 在調用 resolve
和 reject
方法時,須要將隊列中存放的回調按照前後順序依次調用(是否是感受很像瀏覽器的事件環機制)。
class MyPromise {
constructor(exector) {
this.status = MyPromise.PENDING;
this.value = null;
this.reason = null;
/**
* 2.2.6 then may be called multiple times on the same promise
* 2.2.6.1 If/when promise is fulfilled, all respective onFulfilled callbacks must execute in the order of their originating calls to then
* 2.2.6.2 If/when promise is rejected, all respective onRejected callbacks must execute in the order of their originating calls to then.
*/
this.onFulfilledCallback = [];
this.onRejectedCallback = [];
this.initBind();
this.init(exector);
}
initBind() {
// 綁定 this
// 由於 resolve 和 reject 會在 exector 做用域中執行,所以這裏須要將 this 綁定到當前的實例
this.resolve = this.resolve.bind(this);
this.reject = this.reject.bind(this);
}
init(exector) {
try {
exector(this.resolve, this.reject);
} catch (err) {
this.reject(err);
}
}
resolve(value) {
if (this.status === MyPromise.PENDING) {
setTimeout(() => {
this.status = MyPromise.FULFILLED;
this.value = value;
this.onFulfilledCallback.forEach(cb => cb(this.value));
})
}
}
reject(reason) {
if (this.status === MyPromise.PENDING) {
setTimeout(() => {
this.status = MyPromise.REJECTED;
this.reason = reason;
this.onRejectedCallback.forEach(cb => cb(this.reason));
})
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : () => {}
onRejected = typeof onRejected === "function" ? onRejected : () => {}
if (this.status === MyPromise.FULFILLED) {
setTimeout(() => {
try{
onFulfilled(this.value)
}catch(e){
onRejected(e)
}
})
}
if (this.status === MyPromise.REJECTED) {
setTimeout(() => {
try{
onRejected(this.reason);
}catch(e){
onRejected(e)
}
})
}
if (this.status === MyPromise.PENDING) {
// 向對了中裝入 onFulfilled 和 onRejected 函數
this.onFulfilledCallback.push((value) => {
try{
onFulfilled(value)
}catch(e){
onRejected(e)
}
})
this.onRejectedCallback.push((reason) => {
try{
onRejected(reason)
}catch(e){
onRejected(e)
}
})
}
}
}
// 2.1 A promise must be in one of three states: pending, fulfilled, or rejected.
MyPromise.PENDING = "pending"
MyPromise.FULFILLED = "fulfilled"
MyPromise.REJECTED = "rejected"
複製代碼
進行一些測試:
console.log("===START===")
const p4 = new MyPromise(resolve => setTimeout(() => resolve("RESOLVED")));
p4.then(data => console.log(1,data))
p4.then(data => console.log(2,data))
p4.then(data => console.log(3,data))
console.log("===END===")
複製代碼
輸出結果:
===START===
===END===
1 'RESOLVED'
2 'RESOLVED'
3 'RESOLVED'
複製代碼
規範還規定,then
方法必須返回一個新的 Promise 對象,以實現鏈式調用。
2.2.7 then must return a promise. promise2 = promise1.then(onFulfilled, onRejected);
若是 onFulfilled
和 onRejected
是函數,就用函數調用的返回值,來改變新返回的 promise2
對象的狀態。
2.2.7.1 If either
onFulfilled
oronRejected
returns a valuex
, run the Promise Resolution Procedure[[Resolve]](promise2, x)
. 2.2.7.2 If eitheronFulfilled
oronRejected
throws an exceptione
,promise2
must be rejected withe
as the reason.
這裏提到的 Promise Resolution Procedure,實際上是針對 onFulfilled
和 onRejected
方法不一樣返回值的狀況,來對 promise2 的狀態來統一進行處理,咱們暫時先忽略,後文再提供實現。
另外,若是 onFulfilled
和 onRejected
不是函數,那麼就根據當前 promise 對象(promise1)的狀態,來改變 promise2 的狀態。
2.2.7.3 If onFulfilled is not a function and promise1 is fulfilled, promise2 must be fulfilled with the same value as promise1. 2.2.7.4 If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason as promise1.
因爲在前面的代碼,已對 onFulfilled
和 onRejected
函數進行來處理,若是不是函數的話,提供一個默認值:
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : () => {}
onRejected = typeof onRejected === "function" ? onRejected : () => {}
複製代碼
而且每次調用 onFulfilled
或 onRejected
方法時,都會傳入當前實例的 value
或者 reason
屬性,所以對於 onFulfilled
或 onRejected
不是函數的特殊狀況,直接將傳給它們的參數返回便可,promise2 依舊使用 onFulfilled
或 onRejected
的返回值來改變狀態:
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value
onRejected = typeof onRejected === "function" ? onRejected : reason => { throw reason }
複製代碼
上面的方案,還順帶解決了值穿透的問題。所謂值穿透,就是調用 then
方法時,若是不傳入參數,下層鏈條中的 then
方法還可以正常的獲取到 value
或者 reason
值。
new MyPromise(resolve => setTimeout(() => { resolve("Success") }))
.then()
.then()
.then()
...
.then(data => console.log(data));
複製代碼
下面就根據上面的陳述,對 then
方法作進一步的改進:
···
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value
onRejected = typeof onRejected === "function" ? onRejected : reason => { throw reason }
let promise2;
if (this.status === MyPromise.FULFILLED) {
return promise2 = new MyPromise((resolve,reject) => {
setTimeout(() => {
try{
const x = onFulfilled(this.value)
resolve(x);
}catch(e){
reject(e)
}
})
})
}
if (this.status === MyPromise.REJECTED) {
return promise2 = new MyPromise((resolve,reject) => {
setTimeout(() => {
try{
const x = onRejected(this.reason)
resolve(x);
}catch(e){
reject(e)
}
})
})
}
if (this.status === MyPromise.PENDING) {
return promise2 = new MyPromise((resolve,reject) => {
// 向對了中裝入 onFulfilled 和 onRejected 函數
this.onFulfilledCallback.push((value) => {
try{
const x = onFulfilled(value)
resolve(x)
}catch(e){
reject(e)
}
})
this.onRejectedCallback.push((reason) => {
try{
const x = onRejected(reason)
resolve(x);
}catch(e){
reject(e)
}
})
})
}
}
···
複製代碼
規範規定,then
方法必須返回一個新的 Promise 對象(promise2),新的 promise2 的狀態必須依賴於調用 then
方法的 Promise 對象(promise1)的狀態,也就是說,必需要等到 promise1 的狀態變成 fulfilled
或者 rejected
以後,promise2 的狀態才能進行改變。 所以,在 then
方法的實現中,在當前的 Promise 對象(promise1)的狀態爲 pending
時,將改變 promise2
狀態的方法加入到回調函數的隊列中。
上面的代碼,處理了 onFulfilled
和 onRejected
方法的返回值的狀況,以及實現了 then
方法的鏈式調用。 如今考慮一個問題,若是 onFulfilled
或 onRejected
方法返回的是一個 Promise 對象,或者是具備 then
方法的其餘對象(thenable
對象),該怎麼處理呢? 規範中提到,對於 onFulfilled
或 onRejected
的返回值的,提供一個 Promise Resolution Procedure 方法進行統一的處理,以適應不一樣的返回值類型。
2.2.7.1 If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).
咱們將這個方法命名爲 resolvePromise
方法,將其設計爲 MyPromise 類上的一個靜態方法。 resolvePromise
靜態方法的做用,就是根據 onFulfilled
或 onRejected
不一樣的返回值(x
)的狀況,來改變 then
方法返回的 Promise 對象的狀態。 能夠這樣理解:咱們將改變 promise2 對象的狀態的過程,移動到了 resolvePromise
方法中,以便處理更多的細節問題。 下面是 resolvePromise
方法的實現:
MyPromise.resolvePromise = (promise2,x,resolve,reject) => {
let called = false;
/**
* 2.3.1 If promise and x refer to the same object, reject promise with a TypeError as the reason.
*/
if(promise2 === x){
return reject(new TypeError("cannot return the same promise object from onfulfilled or on rejected callback."))
}
if(x instanceof MyPromise){
// 處理返回值是 Promise 對象的狀況
/**
* new MyPromise(resolve => {
* resolve("Success")
* }).then(data => {
* return new MyPromise(resolve => {
* resolve("Success2")
* })
* })
*/
if(x.status === MyPromise.PENDING){
/**
* 2.3.2.1 If x is pending, promise must remain pending until x is fulfilled or rejected.
*/
x.then(y => {
// 用 x 的 fulfilled 後的 value 值 y,去設置 promise2 的狀態
// 上面的注視,展現了返回 Promise 對象的狀況,這裏調用 then 方法的緣由
// 就是經過參數 y 或者 reason,獲取到 x 中的 value/reason
// 拿到 y 的值後,使用 y 的值來改變 promise2 的狀態
// 依照上例,上面生成的 Promise 對象,其 value 應該是 Success2
// 這個 y 值,也有多是新的 Promise,所以要遞歸的進行解析,例以下面這種狀況
/**
* new Promise(resolve => {
* resolve("Success")
* }).then(data => {
* return new Promise(resolve => {
* resolve(new Promise(resolve => {
* resolve("Success3")
* }))
* })
* }).then(data => console.log(data))
*/
// 總之,使用 「return」鏈中最後一個 Promise 對象的狀態,來決定 promise2 的狀態
MyPromise.resolvePromise(promise2, y, resolve, reject)
},reason => {
reject(reason)
})
}else{
/**
* 2.3 If x is a thenable, it attempts to make promise adopt the state of x,
* under the assumption that x behaves at least somewhat like a promise.
*
* 2.3.2 If x is a promise, adopt its state [3.4]:
* 2.3.2.2 If/when x is fulfilled, fulfill promise with the same value.
* 2.3.2.4 If/when x is rejected, reject promise with the same reason.
*/
x.then(resolve,reject)
}
/**
* 2.3.3 Otherwise, if x is an object or function,
*/
}else if((x !== null && typeof x === "object") || typeof x === "function"){
/**
* 2.3.3.1 Let then be x.then.
* 2.3.3.2 If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
*/
try{
// then 方法可能設置了訪問限制(setter),所以這裏進行了錯誤捕獲處理
const then = x.then;
if(typeof then === "function"){
/**
* 2.3.3.2 If retrieving the property x.then results in a thrown exception e,
* reject promise with e as the reason.
*/
/**
* 2.3.3.3.1 If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).
* 2.3.3.3.2 If/when rejectPromise is called with a reason r, reject promise with r.
*/
then.call(x,y => {
/**
* If both resolvePromise and rejectPromise are called,
* or multiple calls to the same argument are made,
* the first call takes precedence, and any further calls are ignored.
*/
if(called) return;
called = true;
MyPromise.resolvePromise(promise2, y, resolve, reject)
},r => {
if(called) return;
called = true;
reject(r);
})
}else{
resolve(x)
}
}catch(e){
/**
* 2.3.3.3.4 If calling then throws an exception e,
* 2.3.3.3.4.1 If resolvePromise or rejectPromise have been called, ignore it.
* 2.3.3.3.4.2 Otherwise, reject promise with e as the reason.
*/
if(called) return;
called = true;
reject(e)
}
}else{
// If x is not an object or function, fulfill promise with x.
resolve(x);
}
}
複製代碼
在我實現規範的規程中,這個 resolvePromise
最最難理解的,主要是 return 鏈這裏,由於想不到具體的場景。我將具體的場景經過註釋的方式寫在上面的代碼中了,一樣迷惑的童鞋能夠看看。
經過 promises-aplus-tests 能夠測試咱們實現的 Promise 類是否知足 Promise/A+ 規範。 進行測試以前,須要爲 promises-aplus-tests 提供一個 deferred
的鉤子:
MyPromise.deferred = function() {
const defer = {}
defer.promise = new MyPromise((resolve, reject) => {
defer.resolve = resolve
defer.reject = reject
})
return defer
}
try {
module.exports = MyPromise
} catch (e) {
}
複製代碼
安裝並運行測試:
npm install promises-aplus-tests -D
npx promises-aplus-tests promise.js
複製代碼
測試結果以下,所有經過:
至此,咱們實現了一個徹底知足 Promise/A+ 規範的 Promise,本文的代碼倉庫在 這裏,歡迎 Star~。完。