前言
之前已經(jīng)學(xué)習(xí)過(guò)了Promise的相關(guān)知識(shí),Promise其實(shí)就是一個(gè)容器,里面保存著某個(gè)未來(lái)才會(huì)結(jié)束的事件(通常是一個(gè)異步操作)的結(jié)果。一般的Promise長(zhǎng)這個(gè)樣子:
const promise = new Promise((resolve, reject) => {
if(/* 一般是異步操作 */) {
resolve('回調(diào)');
} else {
reject('error');
}
});
promise.then((data) => {
console.log(data);
}).catch((error) => {
console.log(error);
});
下面嘗試一下自己去實(shí)現(xiàn)Promise的功能。
基本功能
// 定一個(gè)構(gòu)造函數(shù)
function MyPromise(callBack: Function) {
let thenFunc: ?Function = null;
this.then = (call) => {
thenFunc = call;
};
const thenCall = (value) => {
thenFunc && thenFunc(value);
thenFunc = null;
};
callBack(thenCall);
}
// 使用
const promise = new MyPromise((resolve) => {
setTimeout(() => {
resolve('ssss');
}, 1000);
});
promise.then((data) => {
console.log(data);
});
定義一個(gè)構(gòu)造函數(shù),將初始化的異步方法傳入其中。在函數(shù)內(nèi)部定一個(gè)變量來(lái)存儲(chǔ)promise實(shí)例傳遞進(jìn)來(lái)的then方法,當(dāng)異步方法開(kāi)始執(zhí)行的時(shí)候就會(huì)在函數(shù)內(nèi)部調(diào)用這個(gè)變量存儲(chǔ)的函數(shù),從將參數(shù)回調(diào)至promise實(shí)例中。
初始化的異步方法中,resolve參數(shù)其實(shí)是一個(gè)函數(shù),對(duì)應(yīng)到自定義的函數(shù)中就是thenCall方法,然后當(dāng)resolve調(diào)用的時(shí)候,就是函數(shù)內(nèi)部的用來(lái)存儲(chǔ)then的變量執(zhí)行的時(shí)候。
初始化的方法不是異步的
如果在初始化的時(shí)候,傳入的不是一個(gè)異步的函數(shù),比如下面的
const promise = new MyPromise((resolve) => {
resolve('ssss');
});
由于執(zhí)行順序的問(wèn)題,并不會(huì)回調(diào)到then方法中??梢韵朕k法讓then方法先執(zhí)行,之后再執(zhí)行resolve,利用Js的循環(huán)機(jī)制以及setTimeout,將resolve放入棧底執(zhí)行
const thenCall = (value) => {
setTimeout(() => {
thenFunc && thenFunc(value);
thenFunc = null;
}, 0);
};
如果對(duì)于Js的循環(huán)機(jī)制和setTimeout不是很了解的話可以參考這篇
這樣就可以確保在有then的情況下,resolve在后面執(zhí)行。
promise調(diào)用時(shí)間很晚
如果初始化Promise實(shí)例之后,并沒(méi)有立即使用它,而是間隔了一段時(shí)間再去調(diào)用promise的then方法,那么由于resolve已經(jīng)執(zhí)行完畢,所以即使是調(diào)用過(guò)了then方法也不會(huì)正確的回調(diào)。
setTimeout(() => {
promise.then((data) => {
console.log(data);
}).catch((err) => {
console.log('error' + err);
});
}, 1000);
之前就已經(jīng)介紹過(guò)了,Promise是有三種狀態(tài)的:pending、fulfilled、rejected。而且只能從pending轉(zhuǎn)換為另外兩種狀態(tài),而且是不可逆轉(zhuǎn)的??梢越栌肞romise的這三種狀態(tài),讓自己的Promise記住自己當(dāng)前的狀態(tài)。
添加一個(gè)記錄Promise狀態(tài)的變量,添加一個(gè)存儲(chǔ)參數(shù)的變量
function MyPromise(callBack: Function) {
let thenFunc: ?Function = null;
let status: string = 'pending';
let callValue: any = null;
this.then = (call) => {
if (status === 'pending') {
thenFunc = call;
}
call(callValue);
};
const thenCall = (value) => {
setTimeout(() => {
thenFunc && thenFunc(value);
callValue = value;
status = 'fulfilled';
thenFunc = null;
}, 0);
};
callBack(thenCall);
}
在Promise初始化的時(shí)候,Promise的初始狀態(tài)為pending,執(zhí)行過(guò)thenCall方法后,改變自身狀態(tài)為fulfilled,并且將傳遞的參數(shù)暫時(shí)保存到callValue中。過(guò)一段時(shí)間后,then執(zhí)行的時(shí)候,由于狀態(tài)已經(jīng)改變,則會(huì)直接執(zhí)行then中傳遞的回調(diào)函數(shù),并將callValue作為回調(diào)參數(shù)。
添加reject狀態(tài)
上述的方法都是只有resolve一個(gè)狀態(tài),其實(shí)reject與resolve相同,僅僅是執(zhí)行了不同的操作。
function MyPromise(callBack: Function) {
let thenFunc: ?Function = null;
let catchFunc: ?Function = null;
let status: string = 'pending';
let callValue: any = null;
this.then = (call) => {
if (status === 'pending') {
thenFunc = call;
} else if (status === 'fulfilled') {
call(callValue);
}
return this;
};
this.catch = (call) => {
if (status === 'pending') {
catchFunc = call;
} else if (status === 'rejected') {
call(callValue);
}
return this;
};
const thenCall = (value) => {
setTimeout(() => {
thenFunc && thenFunc(value);
callValue = value;
status = 'fulfilled';
thenFunc = null;
}, 0);
};
const catchCall = (value) => {
setTimeout(() => {
catchFunc && catchFunc(value);
callValue = value;
status = 'rejected';
catchFunc = null;
}, 0);
};
callBack(thenCall, catchCall);
}
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject('ssss');
}, 1000);
});
promise.then((data) => {
console.log(data);
}).catch((err) => {
console.log('error' + err);
});
關(guān)于return this,其實(shí)是將對(duì)象本身返回以供后續(xù)的catch或者其他操作,如果沒(méi)有那么會(huì)報(bào)錯(cuò)說(shuō)沒(méi)有then方法或者沒(méi)有catch方法。
鏈?zhǔn)秸{(diào)用
自身調(diào)用
Promise可以通過(guò)不停地返回Promise對(duì)象從而實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用
promise.then().then().then().....
由于MyPromise內(nèi)部是由一個(gè)變量來(lái)保存的回調(diào)函數(shù),只能保存最新的then方法,要是想實(shí)現(xiàn)自身的鏈?zhǔn)秸{(diào)用,那么就需要將其修改為數(shù)組,把每一個(gè)then傳入的回調(diào)函數(shù)保存起來(lái)。
function MyPromise(callBack: Function) {
let thenFunc: Array<Function> = [];
let catchFunc: ?Function = null;
let status: string = 'pending';
let callValue: any = null;
this.then = (call) => {
if (status === 'pending') {
thenFunc.push(call);
} else if (status === 'fulfilled') {
call(callValue);
}
return this;
};
const thenCall = (value) => {
setTimeout(() => {
callValue = value;
status = 'fulfilled';
thenFunc.forEach((fun) => {
fun && fun(value);
});
}, 0);
};
....
....
callBack(thenCall, catchCall);
}
串行promise
更加常見(jiàn)的需求是不同的Promise類型需要在同一個(gè)方法中鏈?zhǔn)秸{(diào)用
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('ssss');
}, 100);
});
promise.then((data) => {
console.log('11' + data);
return new MyPromise((resolve) => {
setTimeout(() => {
resolve('dddd');
}, 100);
});
}).then((data) => {
console.log('22' + data);
}).catch((err) => {
console.log('error' + err);
});
// 控制臺(tái)輸入的結(jié)果為
// 11ssss
// 22ssss
// 并不是預(yù)期的
// 11ssss
// 22dddd
那么如何讓后續(xù)的then方法,返回的是最新初始化的promise呢?首先考慮的位置就是MyPromise中的then方法。因?yàn)榉祷亓艘粋€(gè)新的promise,而不是像之前的返回this,所以就沒(méi)有辦法處理新的promise里面的內(nèi)容。那么就應(yīng)該區(qū)分出來(lái),then方法里是否返回了一個(gè)新的promise。
this.then = (call) => {
const ret = typeof call === 'function' && call(callValue);
if (ret && ret.then && typeof ret.then === 'function') {
// 返回了一個(gè)新的promise
} else {
// 正常處理
}
};
在then方法中,首先讓傳入的回調(diào)方法執(zhí)行,觀察它的返回值是否是一個(gè)對(duì)象并且里面包含著then方法。如果有的話,該回調(diào)函數(shù)就返回了一個(gè)新的pormise。
上述的方法確實(shí)可以判斷出來(lái)是否傳入了新的promise,但是要明確的一點(diǎn)是,then方法中并不是回調(diào)函數(shù)執(zhí)行的時(shí)候,回調(diào)函數(shù)是否執(zhí)行是由thenCall控制。以上的邏輯判斷,需要傳入thenFunc數(shù)組中存儲(chǔ)起來(lái),在thenCall中執(zhí)行。
const handlePromise = (call: Function, value) => {
const ret = typeof call === 'function' && call(value);
if (ret && ret.then && typeof ret.then === 'function') {
ret.then((data) => {
call(data);
})
}
};
this.then = (call) => {
if (status === 'pending') {
thenFunc.push((value) => { handlePromise(call, value); });
} else if (status === 'fulfilled') {
call(callValue);
}
return this;
};
`將handlePromise傳入thenFunc數(shù)組中,當(dāng)其執(zhí)行的時(shí)候,會(huì)判斷then傳入回調(diào)函數(shù)的返回值。如果是一個(gè)promise對(duì)象,那么就執(zhí)行該promise的then函數(shù)`
// 控制臺(tái)輸出結(jié)果為
// 11ssss
// 22ssss
// 11dddd
控制臺(tái)輸出并不是預(yù)期的結(jié)果,但是好消息是出現(xiàn)第二個(gè)promise的數(shù)據(jù)。那么問(wèn)題在哪里呢,為什么會(huì)輸出三個(gè)結(jié)果?
// 當(dāng)?shù)谝粋€(gè)then執(zhí)行的時(shí)候,執(zhí)行到
const ret = typeof call === 'function' && call(value);
// 因?yàn)檎{(diào)用了call(value);
// 在控制臺(tái)打印了11ssss
// 之后判斷出返回了一個(gè)promise對(duì)象,執(zhí)行到
ret.then((data) => {
call(data);
})
// 這里會(huì)執(zhí)行第二個(gè)promise的異步函數(shù)
setTimeout(() => {
resolve('dddd');
}, 100);
// 即在100ms后,回調(diào)dddd,注意這里面仍然是在第一個(gè)then中回調(diào)!
// 執(zhí)行到這里的時(shí)候控制臺(tái)并沒(méi)有輸出11dddd,而是先輸出了22ssss
// 因?yàn)槭且粋€(gè)100ms延遲的異步函數(shù),在調(diào)用的時(shí)候會(huì)直接執(zhí)行第二個(gè)then
// 此時(shí)MyPromise中的value值仍然是ssss,所以會(huì)直接輸出22ssss
// 100ms后輸出11dddd
到了第二次then方法的時(shí)候,仍然是第一個(gè)值,原因就是return this。仍然返回了本身的promise,而不是第二個(gè)promise中的內(nèi)容。那么可以考慮在then中每次都返回一個(gè)新的promise,用來(lái)接收新的值。
const handlePromise = (call: Function, resolve: Function) => {
const ret = typeof call === 'function' && call(callValue);
if (ret && ret.then && typeof ret.then === 'function') {
ret.then((value) => {
resolve(value);
});
} else {
resolve(ret);
}
};
this.then = (call) => {
return new MyPromise((resolve) => {
if (status === 'pending') {
thenFunc.push(() => { handlePromise(call, resolve); });
} else if (status === 'fulfilled')
call(callValue);
}
});
};
由于在then中構(gòu)建了一個(gè)新的promise,在每次then中都會(huì)調(diào)用resolve來(lái)改變MyPromise中value的值,確保在下一次的then中獲取到新的值。
以上即可在promise中實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用。
相關(guān)API
promise.race()
Promise的race方法其實(shí)是將幾個(gè)promise一起執(zhí)行,首先回調(diào)的promise會(huì)做為race方法的回調(diào)值。
MyPromise.race = (raceList: Array<MyPromise>) => {
return new MyPromise((resolve) => {
let count = 0;
for (let i = 0; i < raceList.length; i++) {
raceList[i].then((value) => {
if (count === 0) {
count++;
resolve(value);
}
});
}
});
};
promise.all()
Promise的all方法是將幾個(gè)promise一起執(zhí)行,當(dāng)每個(gè)promise返回成功的時(shí)候,才會(huì)將所有的結(jié)果組合成一個(gè)數(shù)組返回到結(jié)果中。
MyPromise.all = (allList: Array<MyPromise>) => {
return new MyPromise((resolve) => {
let dataList = [];
for (let i = 0; i < allList.length; i++) {
allList[i].then((value) => {
dataList.push(value);
if (dataList.length == allList.length) {
resolve(dataList);
}
});
}
});
};
小結(jié)
以上是全部關(guān)于自己實(shí)現(xiàn)promise的內(nèi)容,僅僅實(shí)現(xiàn)了promise的一部分內(nèi)容。實(shí)現(xiàn)起來(lái)還是有一點(diǎn)吃力的,尤其是關(guān)于鏈?zhǔn)秸{(diào)用的實(shí)現(xiàn),更加能理解關(guān)于函數(shù)編程的思想,總體來(lái)說(shuō)實(shí)現(xiàn)的比較亂。網(wǎng)上其他大神都是用es5寫(xiě)的,和es6寫(xiě)法還是有一點(diǎn)出入的。使用es6實(shí)現(xiàn)還是比較簡(jiǎn)潔的,如果有任何問(wèn)題請(qǐng)不吝賜教。