是什么
Promise是一種對(duì)異步操作的封裝,主流的規(guī)范是Promise/A+。
Promise可以使得異步代碼層次清晰,便于理解,且更加容易維護(hù)。
追求的效果
那么我們的Promise將要實(shí)現(xiàn)怎樣的效果呢?舉個(gè)簡(jiǎn)單的例子如下:
function p1() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(1);
}, 1000);
});
}
function p2(value) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(2 + value);
}, 1000);
});
}
p1().then(function(res) {
console.log(res); // 1000ms后輸出1
return Promise.resolve(res); // 顯式的return一個(gè)Promise對(duì)象
}).then(p2).then(function(res) {
console.log(res); // 再過1000ms后輸出3
});
我們手寫的Promise最終也可以實(shí)現(xiàn)這個(gè)效果
最基本的構(gòu)建
function Promise(fn) {
let value = null; // 異步函數(shù)執(zhí)行后的結(jié)果
let deferred; // 異步函數(shù)執(zhí)行后,真正要執(zhí)行的回調(diào)函數(shù)
this.then = function(onFulfilled) {
deferred = onFulfilled;
}
function resolve(newValue) {
value = newValue;
deferred(value);
}
fn(resolve);
}
代碼很少,也很容易理解:
- 創(chuàng)建Promise實(shí)例的參數(shù)是fn,并將其內(nèi)部的resolve方法作為參數(shù)傳遞給異步函數(shù)
- Promise的then方法用于注冊(cè)回調(diào)函數(shù),即賦值給內(nèi)部的deferred
- 當(dāng)異步函數(shù)回調(diào)成功后,會(huì)將結(jié)果作為參數(shù)來執(zhí)行resolve,而實(shí)際上是執(zhí)行deferred函數(shù)
- 這樣就實(shí)現(xiàn)了在恰當(dāng)?shù)臅r(shí)機(jī)(異步函數(shù)回調(diào)成功后),執(zhí)行恰當(dāng)?shù)幕卣{(diào)(then注冊(cè)的回調(diào)方法)
改進(jìn)1
目前的代碼只能注冊(cè)一個(gè)回調(diào)方法,這顯然不符合我們的預(yù)期,所以將內(nèi)部的deferred修改為deferreds數(shù)組,相應(yīng)的執(zhí)行resolve時(shí),也要遍歷deferreds數(shù)組依次執(zhí)行:
function Promise(fn) {
let value = null;
let deferreds = [];
this.then = function(onFulfilled) {
deferreds.push(onFulfilled);
}
function resolve(newValue) {
value = newValue;
deferreds.forEach((deferred) => {
deferred(value);
});
}
fn(resolve);
}
改進(jìn)2
實(shí)現(xiàn)then的鏈?zhǔn)秸{(diào)用,非常簡(jiǎn)單:
this.then = function(onFulfilled) {
deferreds.push(onFulfilled);
return this;
}
這樣就可以實(shí)現(xiàn):
p1().then(function(res) {
// do sth. with res
}).then(function(res) {
// do sth. else with res
});
延時(shí)resolve
目前的Promise有一個(gè)bug,假如fn中所包含的是同步代碼,則resolve會(huì)立即執(zhí)行,此時(shí)then還沒有注冊(cè)回調(diào)函數(shù),內(nèi)部的deferreds為空數(shù)組,所以回調(diào)函數(shù)不會(huì)如預(yù)期一樣執(zhí)行。
所以,為resolve添加一個(gè)延時(shí):
function resolve(newValue) {
value = newValue;
setTimeout(() => {
deferreds.forEach((deferred) => {
deferred(value);
});
}, 0);
}
以上保證了resolve于then注冊(cè)回調(diào)函數(shù)之后執(zhí)行。
引入狀態(tài)
目前還存在一點(diǎn)問題,現(xiàn)在用then注冊(cè)回調(diào)函數(shù)的行為都是在異步操作成功之前,一旦異步操作已經(jīng)成功后,內(nèi)部resolve已經(jīng)執(zhí)行完畢,再用then方法注冊(cè)回調(diào)函數(shù)就不會(huì)再執(zhí)行了。
想要解決這個(gè)問題,需要引入規(guī)范中的三個(gè)狀態(tài):pending、fulfilled、rejected,它們之間的關(guān)系是:

引入狀態(tài)后的代碼:
function Promise(fn) {
let state = "pending";
let value = null;
let deferreds = [];
this.then = function(onFulfilled) {
// state若為pending則將onFulfilled加入隊(duì)列
if(state === "pending") {
deferreds.push(onFulfilled);
return this;
}
// state若為fulfilled則立即執(zhí)行onFulfilled
onFulfilled(value);
return this;
}
function resolve(newValue) {
state = "fulfilled"; // 異步操作完成后將state置為fulfilled
value = newValue;
setTimeout(() => {
deferreds.forEach((deferred) => {
deferred(value);
});
}, 0);
}
fn(resolve);
}
異步操作成功之后,state會(huì)變成fulfilled,這之后then注冊(cè)的回調(diào)函數(shù)都會(huì)立即執(zhí)行。
以上基本實(shí)現(xiàn)了Promise的所有基礎(chǔ)功能,但真正魔幻般的部分尚未開始,詳見下篇~
參考資料:剖析 Promise 之基礎(chǔ)篇