Promise
代表一個異步操作的結(jié)果,使用Promise
我們可以:
- 擺脫回調(diào)地獄;
- 更易讀的代碼约巷,函數(shù)參數(shù)左冬、返回值一目了然。比較下面兩個函數(shù):
// 額外的callback參數(shù)讓人對函數(shù)的輸入和輸出類型感到困惑
function asyncFn(argu: number, callback): void {
let value: string = '';
...
callback(err, value)
}
function asyncFn(argu: number): Promise<string> {
return new Promise((resolve, reject) => {
let value: string = '';
...
if (err) {
reject(err)
}
resolve(value)
})
}
- 更容易地處理異步錯誤;
通過嘗試實(shí)現(xiàn)Promise
沪饺,可以對Promise
的行為有更深的理解者甲。
實(shí)現(xiàn)Promise
先從閱讀規(guī)范開始怜姿。
規(guī)范解讀
Promise
規(guī)范由3個部分組成:
- Promise狀態(tài)
- then方法
- Promise Resolution Procedure
下面依次對這3部分進(jìn)行解讀慎冤,其中忽略了異常處理部分和一些細(xì)節(jié)。
Promise狀態(tài)
-
promise
的狀態(tài)必為pending
沧卢、fulfilled
和rejected
這三個狀態(tài)中的一個蚁堤。 - 當(dāng)
promise
為pending
狀態(tài)時(shí),可能會轉(zhuǎn)變?yōu)?code>fulfilled狀態(tài)或rejected
狀態(tài)但狭。 - 當(dāng)
promise
為fulfilled
狀態(tài)時(shí)披诗,必須擁有一個value
,并且不會再變化立磁。 - 當(dāng)
promise
為rejected
狀態(tài)時(shí)呈队,必須擁有一個reason
,并且不會再變化唱歧。
then方法
-
promise
必須提供一個then
方法宪摧,通過then
方法可獲取最終的value
或reason
。 -
then
方法接收兩個參數(shù)颅崩,并返回一個promise
:
promise2 = promise1.then(onFulfilled?, onRejected?)
-
onFulfilled
和onRejected
是可選參數(shù)几于。 - 如果
onFulfilled
是一個函數(shù),它將僅在promise1
變?yōu)?code>fulfilled狀態(tài)時(shí)被調(diào)用一次沿后,value
是它的第一個參數(shù)沿彭。 - 如果
onRejected
是一個函數(shù),它將僅在promise1
變?yōu)?code>rejected狀態(tài)時(shí)被調(diào)用一次尖滚,reason
是它的第一個參數(shù)喉刘。 - 當(dāng)
onFulfilled
不是函數(shù),并且promise1
是fulfilled
狀態(tài)時(shí)熔掺,promise2
的狀態(tài)必須也是fulfilled
饱搏,并且擁有和promise1
一樣的value
非剃。引用MDN
的話說置逻,即,當(dāng)onFulfilled
不是函數(shù)時(shí)备绽,會用identity function
(返回它接收的參數(shù))替代券坞。 - 當(dāng)
onRejected
不是函數(shù),并且promise1
是rejected
狀態(tài)時(shí)肺素,promise2
的狀態(tài)必須也是rejected
恨锚,并且擁有和promise1
一樣的reason
。引用MDN
的話說倍靡,即猴伶,當(dāng)onRejected
不是函數(shù)時(shí),會用thrower function
(拋出它接收的參數(shù))替代。 -
onFulfilled
和onRejected
必須被異步調(diào)用他挎。以避免unleash Zalgo
筝尾。也就是要保證onFulfilled
和onRejected
始終是異步調(diào)用的,避免出現(xiàn)有時(shí)異步有時(shí)同步的情況办桨。比如下面這個例子筹淫,console.log
始終會被異步執(zhí)行。
new Promise((resolve, reject) => {
if (Math.random() >= 0.5) {
setTimeout(() => resolve(0), 5000)
return;
}
resolve(1)
}).then(console.log)
-
onFulfilled
和onRejected
必須被當(dāng)作函數(shù)調(diào)用(沒有this
)呢撞。 -
then
方法可以在同一個promise
上多次調(diào)用损姜。相應(yīng)的onFulfilled/onRejected
被調(diào)用的順序和原本調(diào)用then
方法的順序相同。 -
onFulfilled
和onRejected
任一個拋出錯誤e
殊霞,promise2
會變成rejected
狀態(tài)摧阅,并以錯誤e
作為reason
。 -
onFulfilled
和onRejected
任一個返回x
绷蹲,promise2
會執(zhí)行Promise Resolution
過程:[[Resolve]](promise2, x)
逸尖。
Promise Resolution過程
Promise Resolution
過程即promise.resolve(x)
的過程,規(guī)范中標(biāo)記為[[Resolve]](promise, x)
瘸右。
首先這里涉及一個概念:thenable
娇跟,如果x
是object
或者function
類型,并且有then
方法太颤,那么x
就是thenable
苞俘,顯然,promise
對象是thenable
龄章。
如果x
不是thenable
吃谣,Promise Resolution
過程就是將promise
變?yōu)?code>fulfilled狀態(tài),并以x
作為fulfilled value
做裙。
如果x
是thenable
岗憋,promise
會嘗試采用x
的狀態(tài)。即锚贱,x
是pending
狀態(tài)時(shí)仔戈,promise
也是pending
狀態(tài);如果x
變成fulfilled
狀態(tài)拧廊,promise
也變成fulfilled
狀態(tài)监徘,并用x
的fulfilled value
作為promise
的fulfilled value
;如果x
變成rejected
狀態(tài)吧碾,promise
也變成rejected
狀態(tài)凰盔,并用x
的rejected reason
作為promise
的rejected reason
。
因此倦春,會存在thenable chain
的情況:
thenable chain
的長度不會被限制户敬,可以鏈任意多的thenable
落剪。但是,考慮到
[[Resolve]](promise1, promise1)
會導(dǎo)致無限循環(huán)尿庐,所以這種情況下應(yīng)該拋出TypeError
著榴。如:
以下3種場景都會執(zhí)行[[Resolve]](promise, x)
過程
// 1.
promise.resolve(x);
// 2.
promise = new Promise(function(resolve, reject) {
resolve(x);
})
// 3.
promise = promise1.then(function() {
return x;
})
實(shí)現(xiàn)
為了便于理解,將promise
實(shí)現(xiàn)拆分成若干步驟屁倔,逐步實(shí)現(xiàn)脑又。
狀態(tài)機(jī)
由于Promise
本身是一個狀態(tài)機(jī),所以從這里開始锐借。Promise
初始狀態(tài)是pending
问麸,在fulfill
或reject
時(shí),更新狀態(tài)钞翔。
function Promise() {
var value, reason, state = 'pending';
function beFulfilled(result) {
state = 'fulfilled';
value = result;
}
function beRejected(error) {
state = 'rejected';
reason = error;
}
}
Promise Resolution過程
Promise Resolution Procedure
是一個遞歸的過程严卖,直至所有thenable
都完成。
function Promise(fn) {
var value, reason, state = 'pending';
function beFulfilled(result) {
state = 'fulfilled';
value = result;
}
function beRejected(error) {
state = 'rejected';
reason = error;
}
function doResolution(x) {
var type = typeof x
if ((type === 'object' || type === 'function') && typeof x.then === 'function') {
x.then(function(val) {
doResolution(val);
}, function(err) {
beRejected(err)
})
}
beFulfilled(x)
}
fn(doResolution, beRejected)
}
then方法onFulfilled/onRejected執(zhí)行
then
方法可能在fulfilled/rejected
之前或者之后被調(diào)用布轿,也可以被多次調(diào)用哮笆。如果調(diào)用then
時(shí),promise
還處于pending
狀態(tài)汰扭,應(yīng)該先將onFulfilled/onRejected
暫存起來稠肘。在promise
變?yōu)?code>fulfilled/rejected之后,依次執(zhí)行暫存的onFulfilled/onRejected
萝毛。
function Promise(fn) {
var value,
reason,
state = 'pending',
handlers = [];
function beFulfilled(result) {
state = 'fulfilled';
value = result;
handlers.forEach(handle);
handlers = [];
}
function beRejected(error) {
state = 'rejected';
reason = error;
handlers.forEach(handle);
handlers = [];
}
function doResolution(x) {
var type = typeof x
if ((type === 'object' || type === 'function') && typeof x.then === 'function') {
x.then(function(val) {
doResolution(val);
}, function(err) {
beRejected(err)
})
}
beFulfilled(x)
}
function handle(handler) {
if (state === 'pending') {
handlers.push(handler);
return;
} else if (state === 'fulfilled') {
handler.onFulfilled.call(null, value)
} else if (state === 'rejected') {
handler.onRejected.call(null, reason)
}
}
this.then = function(onFulfilled, onRejected) {
handle({
onFulfilled,
onRejected,
});
}
fn(doResolution, beRejected)
}
then方法返回promise
then
方法需要返回一個promise
项阴,返回的promise
依賴onFulfilled/onRejected
執(zhí)行結(jié)果,所以將返回的promise
的resolve/reject
和then
方法的onFulfilled/onRejected
保存在一起笆包,在onFulfilled/onRejected
執(zhí)行后調(diào)用resolve/reject
环揽。
function Promise(fn) {
var value,
reason,
state = 'pending',
handlers = [];
function beFulfilled(result) {
state = 'fulfilled';
value = result;
handlers.forEach(handle);
handlers = [];
}
function beRejected(error) {
state = 'rejected';
reason = error;
handlers.forEach(handle);
handlers = [];
}
function doResolution(x) {
var type = typeof x
if ((type === 'object' || type === 'function') && typeof x.then === 'function') {
x.then(function(val) {
doResolution(val);
}, function(err) {
beRejected(err)
})
}
beFulfilled(x)
}
function handle(handler) {
if (state === 'pending') {
handlers.push(handler);
return;
} else if (state === 'fulfilled') {
try {
handler.resolve(handler.onFulfilled.call(null, value))
} catch(e) {
handler.reject(e)
}
} else if (state === 'rejected') {
try {
handler.resolve(handler.onRejected.call(null, reason))
} catch(e) {
handler.reject(e);
}
}
}
this.then = function(onFulfilled, onRejected) {
return new Promise(function(resolve, reject) {
handle({
onFulfilled,
onRejected,
resolve,
reject,
});
})
}
fn(doResolution, beRejected)
}
至此,基本實(shí)現(xiàn)了promise
庵佣,最后歉胶,補(bǔ)充完整錯誤處理和其他細(xì)節(jié),代碼如下:
function Promise(fn) {
var value,
reason,
state = 'pending',
handlers = [],
/**
* 當(dāng)resolve/reject被多次調(diào)用時(shí)巴粪,只有第一次調(diào)用有效通今。
* 如:
* new Promise((resolve, reject) => {
* resolve(new Promise(r => setTimeout(() => r(1), 5000)))
* resolve(new Promise(r => setTimeout(() => r(2), 3000)))
* }).then(console.log)
**/
invoked = false,
self = this;
// 考慮typeof null === 'object',所以判斷類型是否為object或function验毡,不能簡單通過typeof判斷
function typeOf(obj) {
return Object.prototype.toString.call(obj).match(/^\[object\s+(\w+)\]$/)[1].toLowerCase();
}
function once(func) {
return function() {
if (invoked) return;
invoked = true;
func.apply(null, arguments);
}
}
function beFulfilled(result) {
state = 'fulfilled';
value = result;
handlers.forEach(handle);
handlers = [];
}
function beRejected(error) {
state = 'rejected';
reason = error;
handlers.forEach(handle);
handlers = [];
}
function doResolution(x) {
/**
* 如果onFulfilled/onRejected被多次調(diào)用衡创,只有第一次調(diào)用有效。
* 如:
* new Promise((resolve, reject) => {
* resolve({
* then(onFulfilled, onRejected) {
* onFulfilled(new Promise(r => setTimeout(() => r(1), 5000)));
* onFulfilled(new Promise(r => setTimeout(() => r(2), 3000)));
* }
* })
* }).then(console.log)
*/
var called = false;
if (self === x) {
beRejected(new TypeError('Chaining cycle detected'));
return;
}
var type = typeOf(x);
if (type === 'object' || type === 'function') {
try {
var then = x.then;
if (typeOf(then) === 'function') {
try {
then.call(x, function(val) {
if (called) return;
called = true;
doResolution(val);
}, function(err) {
if (called) return;
called = true;
beRejected(err);
})
} catch(e) {
if (called) return;
called = true;
beRejected(e);
}
} else {
beFulfilled(x);
}
} catch(e) {
beRejected(e);
}
} else {
beFulfilled(x);
}
}
function handle(handler) {
if (state === 'pending') {
handlers.push(handler);
return;
} else if (state === 'fulfilled') {
var x, onFulfilled = typeOf(handler.onFulfilled) === 'function' ? handler.onFulfilled : function(v) {
return v;
};
setTimeout(function() {
try {
x = onFulfilled(value);
} catch(e) {
handler.reject(e);
return;
}
handler.resolve(x);
});
} else if (state === 'rejected') {
var x, onRejected = typeOf(handler.onRejected) === 'function' ? handler.onRejected : function(r) {
throw r;
};
setTimeout(function() {
try {
x = onRejected(reason);
} catch(e) {
handler.reject(e);
return;
}
handler.resolve(x);
});
}
}
this.then = function(onFulfilled, onRejected) {
return new Promise(function(resolve, reject) {
handle({
onFulfilled,
onRejected,
resolve,
reject,
});
});
};
fn(once(doResolution), once(beRejected));
}
完整代碼已經(jīng)通過Promises/A+ Compliance Test Suite全部測試用例晶通。
// 測試文件,我將上面實(shí)現(xiàn)的Promise取名Qromise
const Qromise = require('../Qromise')
describe("Promises/A+ Tests", function () {
require("promises-aplus-tests").mocha({
resolved(value) {
return new Qromise(resolve => resolve(value));
},
rejected(reason) {
return new Qromise((...[, reject]) => reject(reason));
},
deferred() {
const obj = {}
obj.promise = new Qromise((resolve, reject) => {
obj.resolve = value => resolve(value);
obj.reject = reason => reject(reason);
})
return obj
},
});
});