實現一個Promise(基於Promise/A+規範)
前言
相信大家經常使用Promise
,或者使用Generator
、asnyc/await
等非同步解決方案,網上的Promise
原理也遍地開花。
一直以來想抽出時間也寫一寫Promise
實現,但是平常工作也是忙的不可開交,正好元旦放了3天假期,休息了2天半,抽出半天時間來看一看Promise
。
如何使用Promise
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000)
}).then((data) => {
console.log(data);
return new Promise((res) => {
set Timeout(() => {
res(2);
},1000)
})
}).then((res) => {
console.log(res);
})
複製程式碼
術語
Promise
是一個包含了相容promise
規範then
方法的物件或函式。thenable
是一個包含了then
方法的物件或函式。value
是任何Javascript
值。 (包括undefined
,thenable
,Promise
等)。exception
是由throw
表示式丟擲來的值。reason
是一個用於描述Promise
被拒絕原因的值。
由於Promise/A+
catch
、race
、all
等方法的實現,所以這裡也不會去詳細解釋。
要求
一個Promise
必須處在其中之一的狀態:pending
, fulfilled
或 rejected
。 如果是pending
狀態,則promise
:
可以轉換到fulfilled
或rejected
狀態。 如果是fulfilled
狀態,則promise
:
- 不能轉換成任何其它狀態。
- 必須有一個值,且這個值不能被改變。
如果是rejected
狀態,則promise
可以:
- 不能轉換成任何其它狀態。
- 必須有一個原因,且這個值不能被改變。
function MyPromise(callback ) {
let that = this;
//定義初始狀態
//Promise狀態
that.status = 'pending';
//value
that.value = 'undefined';
//reason 是一個用於描述Promise被拒絕原因的值。
that.reason = 'undefined';
//定義resolve
function resolve(value) {
//當status為pending時,定義Javascript值,定義其狀態為fulfilled
if(that.status === 'pending') {
that.value = value;
that.status = 'resolved';
}
}
//定義reject
function reject(reason) {
//當status為pending時,定義reason值,定義其狀態為rejected
if(that.status === 'pending') {
that.reason = reason;
that.status = 'rejected';
}
}
//捕獲callback是否報錯
try {
callback(resolve, reject);
} catch (error) {
reject(error);
}
}
複製程式碼
Promise
物件有一個then
方法,用來註冊在這個Promise
狀態確定後的回撥,then
方法接受兩個引數: Promise.then(onFulfilled,onRejected)
。
我們把then函式寫在原型上。
MyPromise.prototype.then = function(onFulfilled, onRejected) {
let that = this;
if(that.status === 'resolved') {
onFulfilled(that.value);
}
if(that.status === 'rejected') {
onRejected(that.reason);
}
}
複製程式碼
上述程式碼只是實現了Promise
的最基本邏輯,如果直接呼叫then
是可以執行的,但是並不支援非同步,而Promise
最大的特點就是解決callback
非同步回撥地獄的問題。
所以我們來改造下。
function MyPromise(callback) {
let that = this;
//定義初始狀態
//Promise狀態
that.status = 'pending';
//value
that.value = 'undefined';
//reason 是一個用於描述Promise被拒絕原因的值。
that.reason = 'undefined';
//用來解決非同步問題的陣列
that.onFullfilledArray = [];
that.onRejectedArray = [];
//定義resolve
function resolve(value) {
//當status為pending時,定義Javascript值,定義其狀態為fulfilled
if(that.status === 'pending') {
that.value = value;
that.status = 'resolved';
that.onFullfilledArray.forEach((func) => {
func(that.value);
});
}
}
//定義reject
function reject(reason) {
//當status為pending時,定義reason值,定義其狀態為rejected
if(that.status === 'pending') {
that.reason = reason;
that.status = 'rejected';
that.onRejectedArray.forEach((func) => {
func(that.reason);
});
}
}
//捕獲callback是否報錯
try {
callback(resolve, reject);
} catch (error) {
reject(error);
}
}
複製程式碼
then
函式的改造
MyPromise.prototype.then = function(onFulfilled, onRejected) {
let that = this;
//需要修改下,解決非同步問題,即當Promise呼叫resolve之後再呼叫then執行onFulfilled(that.value)。
//用兩個陣列儲存下onFulfilledArray
if(that.status === 'pending') {
that.onFullfilledArray.push((value) => {
onFulfilled(value);
});
that.onRejectedArray.push((reason) => {
onRejected(reason);
});
}
if(that.status === 'resolved') {
onFulfilled(that.value);
}
if(that.status === 'rejected') {
onRejected(that.reason);
}
}
複製程式碼
由於Promise/A+
規範規定一個Promise
必須處在其中之一的狀態:pending
, fulfilled
或 rejected
,所以在使用者使用Promise
時,寫的是非同步程式碼的話,那麼此時Promise
一定是處於pending
狀態,反之正常呼叫。
因此,初始化Promise
時,定義兩個陣列為onFullfilledArray
,onRejectedArray
,用來儲存then
函式的兩個回撥函式onFulfilled
和onRejected
。同時我們在then
函式中判斷status
是否是pending
,然後將onFulfilled
和onRejected
分別傳入對應陣列中。當用戶呼叫resolve
或reject
時,更改狀態,遍歷陣列,執行onFulfilled
或者onRejected
,從而非同步呼叫。
then函式的鏈式呼叫
在Promise/A+
規範中:
對於一個promise,它的then方法可以呼叫多次
- 當
promise
fulfilled
後,所有onFulfilled
都必須按照其註冊順序執行。 - 當
promise
rejected
後,所有OnRejected
都必須按照其註冊順序執行。
then 必須返回一個promise
- 如果
onFulfilled
或onRejected
返回了值x
, 則執行Promise
解析流程[[Resolve]](promise2, x)
。 - 如果
onFulfilled
或onRejected
丟擲了異常e
, 則promise2
應當以e
為reason
被拒絕。 - 如果
onFulfilled
不是一個函式且promise1
已經fulfilled
,則promise2
必須以promise1
的值fulfilled
。 - 如果
OnReject
不是一個函式且promise1
已經rejected
, 則promise2
必須以相同的reason
被拒絕。
MyPromise.prototype.then = function(onFulfilled, onRejected) {
let that = this;
let promise2;
// 根據標準,如果then的引數不是function,則我們需要忽略它
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function(f) {};
onRejected = typeof onRejected === 'function' ? onRejected : function(r) {};
//需要修改下,解決非同步問題,即當Promise呼叫resolve之後再呼叫then執行onFulfilled(that.value)。
//用兩個陣列儲存下onFulfilledArray
if(that.status === 'pending') {
return promise2 = new Promise(function(resolve, reject) {
that.onFullfilledArray.push((value) => {
try {
let x = onFulfilled(that.value);
//判斷onFulfilled是否是一個Promise,如果是,那麼就直接把MyPromise中的resolve和reject傳給then;
//返回值是一個Promise物件,直接取它的結果做為promise2的結果
if(x instanceof MyPromise) {
x.then(resolve, reject);
}
//否則,以它的返回值做為promise2的結果
resolve(x);
} catch (error) {
reject(error);
}
});
that.onRejectedArray.push((value) => {
try {
let x = onRejected(that.value);
//判斷onRejected是否是一個Promise,如果是,那麼就直接把MyPromise中的resolve和reject傳給then;
//返回值是一個Promise物件,直接取它的結果做為promise2的結果
if(x instanceof MyPromise) {
x.then(resolve, reject);
}
//否則,以它的返回值做為promise2的結果
resolve(x);
} catch (error) {
reject(error);
}
});
})
}
if(that.status === 'fulfilled') {
return promise2 = new MyPromise(function(resolve, reject) {
try {
let x = onFulfilled(that.value);
//判斷onFulfilled是否是一個Promise,如果是,那麼就直接把MyPromise中的resolve和reject傳給then;
//返回值是一個Promise物件,直接取它的結果做為promise2的結果
if(x instanceof MyPromise) {
x.then(resolve, reject);
}
//否則,以它的返回值做為promise2的結果
resolve(x);
} catch (error) {
reject(error);
}
})
}
if(that.status === 'rejected') {
return new MyPromise(function(resolve, reject) {
try {
let x = onRejected(that.value);
//判斷onRejected是否是一個Promise,如果是,那麼就直接把MyPromise中的resolve和reject傳給then;
//返回值是一個Promise物件,直接取它的結果做為promise2的結果
if(x instanceof MyPromise) {
x.then(resolve, reject);
}
//否則,以它的返回值做為promise2的結果
resolve(x);
} catch (error) {
reject(error);
}
})
}
}
複製程式碼
在呼叫then
時,判斷onFulfilled
和onRejected
是否是一個函式,如果不是,返回一個匿名函式,同時必須返回各引數的值,用來解決鏈式呼叫時Promise
值的穿透問題。
例如:
new MyPromise(resolve=>resolve(8))
.then()
.then()
.then(function foo(value) {
alert(value)
})
複製程式碼
所以我們把這塊改成這樣:
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function(f) { return f};
onRejected = typeof onRejected === 'function' ? onRejected : function(r) {throw r};
複製程式碼
但是這樣每次判斷都需要重新寫這個x
和MyPromise
的關係,所以,我們需要將這塊程式碼給抽象出來,這塊程式碼在Promise/A+
規範中叫做resolvePromise
。
function resolvePromise(promise, x, resolve, reject) {
let then,thenCalledOrThrow = false
//如果promise 和 x 指向相同的值, 使用 TypeError做為原因將promise拒絕。
if (promise === x) {
return reject(new TypeError('Chaining cycle detected for promise!'))
}
//判斷x是否是一個Promise,如果是,那麼就直接把MyPromise中的resolve和reject傳給then;
//返回值是一個Promise物件,直接取它的結果做為promise2的結果
if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
try {
then = x.then
if (typeof then === 'function') { // typeof
//x.then(resolve, reject);
then.call(x, function rs(y) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return resolvePromise(promise, y, resolve, reject)
}, function rj(r) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return reject(r)
})
} else {
return resolve(x)
}
} catch(e) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return reject(e)
}
} else {
return resolve(x)
}
}
複製程式碼
then
函式最後修改為:
MyPromise.prototype.then = function(onFulfilled, onRejected) {
let that = this;
let promise2;
// 根據標準,如果then的引數不是function,則我們需要忽略它
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function(f) { return f};
onRejected = typeof onRejected === 'function' ? onRejected : function(r) {throw r};
//需要修改下,解決非同步問題,即當Promise呼叫resolve之後再呼叫then執行onFulfilled(that.value)。
//用兩個陣列儲存下onFulfilledArray
if(that.status === 'pending') {
return promise2 = new Promise(function(resolve, reject) {
that.onFullfilledArray.push((value) => {
try {
let x = onFulfilled(value);
resolvePromise(promise2, x, resolve, reject)
} catch(e) {
return reject(e)
}
});
that.onRejectedArray.push((value) => {
try {
let x = onRejected(value);
resolvePromise(promise2, x, resolve, reject)
} catch(e) {
return reject(e)
}
});
})
}
if(that.status === 'fulfilled') {
return promise2 = new MyPromise(function(resolve, reject) {
try {
let x = onFulfilled(that.value);
//處理then的多種情況
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error);
}
})
}
if(that.status === 'rejected') {
return new MyPromise(function(resolve, reject) {
try {
let x = onRejected(that.value);
//處理then的多種情況
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error)
}
})
}
}
複製程式碼
測試一下:
new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000)
}).then((data) => {
console.log(data);
return new MyPromise((res) => {
setTimeout(() => {
res(2);
},1000)
})
}).then((res) => {
console.log(res);
})
//1
//2
複製程式碼
最後
Promise GITHUB地址。
參考資料:
《Promise/A+規範》
《Promise3》
《實現一個完美符合Promise/A+規範的Promise》
《剖析Promise內部結構,一步一步實現一個完整的、能通過所有Test case的Promise類》