Promise的簡(jiǎn)易實(shí)現(xiàn)(1)
1.Promise的日常使用
舉個(gè)栗子:
function getMsg() {
return new Promise(function(resolve) {
//異步請(qǐng)求
http.get(url,function(msg){
resolve(msg);
})
})
}
getMsg()
.then(res => console.log(res))
2.做出Promise大致框架
function Promise(fn) {
var value = null,
callbacks = [];//callbacks為數(shù)組帕膜,因?yàn)榭赡芡瑫r(shí)有很多個(gè)回調(diào)
this.then = function (onFulfilled) {
//執(zhí)行then函數(shù)的時(shí)候舌菜,添加所有的callback方法
callbacks.push(onFulfilled);
//return this支持鏈?zhǔn)秸{(diào)用
return this;
};
function resolve(value) {
//外部執(zhí)行resolve函數(shù)時(shí),將所有的callback方法依次執(zhí)行
callbacks.forEach(function (callback) {
callback(value);
});
}
fn(resolve);
}
代碼的大致邏輯:
- 調(diào)用then方法,將想要在Promise異步操作成功時(shí)執(zhí)行的回調(diào)放入callbacks隊(duì)列,其實(shí)也就是注冊(cè)回調(diào)函數(shù),可以向觀察者模式方向思考己儒;
- 創(chuàng)建Promise實(shí)例時(shí)傳入的函數(shù)會(huì)被賦予一個(gè)函數(shù)類(lèi)型的參數(shù),即resolve捆毫,它接收一個(gè)參數(shù)value闪湾,代表異步操作返回的結(jié)果,當(dāng)一步操作執(zhí)行成功后绩卤,用戶會(huì)調(diào)用resolve方法途样,這時(shí)候其實(shí)真正執(zhí)行的操作是將callbacks隊(duì)列中的回調(diào)一一執(zhí)行;
3.實(shí)現(xiàn)Promise延遲機(jī)制
細(xì)心的小明發(fā)現(xiàn)了一個(gè)問(wèn)題濒憋,如果在then方法注冊(cè)回調(diào)之前何暇,resolve函數(shù)就執(zhí)行了,怎么辦凛驮?比如promise內(nèi)部的函數(shù)是同步函數(shù):
function getMsg() {
return new Promise(function(resolve) {
//異步請(qǐng)求
resolve(123);
})
}
Promises/A+規(guī)范明確要求回調(diào)需要通過(guò)異步方式執(zhí)行裆站,用以保證一致可靠的執(zhí)行順序。因此我們要加入一些處理黔夭,保證在resolve執(zhí)行之前宏胯,then方法已經(jīng)注冊(cè)完所有的回調(diào)。這個(gè)時(shí)候就需要我們實(shí)現(xiàn)resolve方法的延時(shí)機(jī)制本姥,使用setTimeout方法:
function resolve(value) {
setTimeout(function() {
callbacks.forEach(function (callback) {
callback(value);
});
}, 0)
}
setTimeout會(huì)將callback的異步執(zhí)行隊(duì)列放在下一個(gè)macrotask里面肩袍,等執(zhí)行了macrotask中的所有microtask后再執(zhí)行下一個(gè)macrotask。具體可查詢Javascript的event loop相關(guān)知識(shí)婚惫。
4.完整Promise實(shí)現(xiàn)
1.Promise狀態(tài)的引入
在上述代碼中氛赐,我們大致實(shí)現(xiàn)了Promise的基本功能魂爪,但小明通過(guò)敏銳的洞察力可觀察到,即使在執(zhí)行resolve會(huì)依次執(zhí)行callback的所有注冊(cè)方法艰管,這樣額外開(kāi)銷(xiāo)會(huì)非常大滓侍,這顯然并不是我們想要的Promise,所以我們需要給Promise增加狀態(tài)牲芋,當(dāng)狀態(tài)切換時(shí)粗井,執(zhí)行相應(yīng)的回調(diào)方法。三種狀態(tài)分別為pending街图、fulfilled、rejected懒构,對(duì)應(yīng)相應(yīng)的回調(diào)方法餐济。
2.Promise狀態(tài)的相互轉(zhuǎn)換
Promises/A+規(guī)范Promise States中明確規(guī)定了,pending可以轉(zhuǎn)化為fulfilled或rejected并且只能轉(zhuǎn)化一次胆剧,也就是說(shuō)如果pending轉(zhuǎn)化到fulfilled狀態(tài)絮姆,那么就不能再轉(zhuǎn)化到rejected。也就是說(shuō)在Promise的狀態(tài)切換是不可逆的秩霍。并且fulfilled和rejected狀態(tài)只能由pending轉(zhuǎn)化而來(lái)篙悯,兩者之間不能互相轉(zhuǎn)換×迦蓿可以看下圖之前的切換:
代碼的思路是這樣的:resolve執(zhí)行時(shí)鸽照,會(huì)將狀態(tài)設(shè)置為fulfilled,在此之后調(diào)用then添加的新回調(diào)颠悬,都會(huì)立即執(zhí)行矮燎。
function Promise(fn) {
var state = 'pending',
value = null,
callbacks = []; //callbacks為數(shù)組,因?yàn)榭赡芡瑫r(shí)有很多個(gè)回調(diào)
this.then = function (onFulfilled) {
if (state === 'pending') {
callbacks.push(onFulfilled);
//return this支持鏈?zhǔn)秸{(diào)用
return this;
}
//假如state已經(jīng)切換至其他狀態(tài)赔癌,直接執(zhí)行回調(diào)
onFulfilled(value);
return this;
};
function resolve(newValue) {
state = 'fulfilled';
value = newValue;
setTimeout(function () {
callbacks.forEach(function (callback) {
callback(newValue);
});
}, 0);
}
fn(resolve);
}
3.鏈?zhǔn)絇romise
但聰明的小明又發(fā)現(xiàn)了一個(gè)問(wèn)題诞外,當(dāng)在then里面執(zhí)行一個(gè)回調(diào)時(shí),假如在這里面返回一個(gè)Promise灾票,類(lèi)似于這樣:
getMsg()
.then(getDetailMsg)
.then(function (detailMsg) {
// 對(duì)detailMsg的處理
});
function getDetailMsg(msg) {
return new Promise(function (resolve) {
http.get(url + msg, function(detailMsg) {
resolve(detailMsg);
});
});
}
這就是傳說(shuō)中的鏈?zhǔn)絇romise循環(huán)峡谊,在第二個(gè)then中,我們希望它能掛鉤到第一個(gè)Promise所resolve的參數(shù)刊苍。要達(dá)到我們想要的效果既们,需要修改then的源碼,使其返回一個(gè)新的Promise班缰。作為Promise實(shí)現(xiàn)最大的難題贤壁,這個(gè)我們后面繼續(xù)實(shí)現(xiàn)。