手寫promise幾乎每家大廠的必備考題翻具,幾次面試都被這個問題坑了峰锁,于是花了些時間特意研究了一下,下面是promise實現(xiàn)的思考過程坑填。大家如果不嫌棄抛人,還請往下看:
promise的介紹
所謂Promise,簡單說就是一個容器脐瑰,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果妖枚。從語法上說,Promise 是一個對象蚪黑,從它可以獲取異步操作的消息盅惜。
例如以下一個promise的例子:
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 異步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
可以看出一個promise的構造函數(shù)包含兩個方法resolve中剩、reject,同時根據(jù)promise+規(guī)范可知promise包含三個狀態(tài):
- pending: 初始狀態(tài),既不是成功抒寂,也不是失敗狀態(tài)结啼。
- fulfilled: 意味著操作成功完成。
- rejected: 意味著操作失敗屈芜。
那么我們可以可以根據(jù)這三種不同狀態(tài)去實現(xiàn)resolve郊愧、reject,以及實現(xiàn)then方法井佑,那么一個簡單的promise雛形就出來了属铁。下面來實現(xiàn)它:
promise構造函數(shù):
首先可以根據(jù)上面的推測寫個構造函數(shù)如下:
/**
* 創(chuàng)建三變量記錄表示狀態(tài)
* 用that保存this,避免后期閉包導致this的指向不對
* value 變量用于保存 resolve 或者 reject 中傳入的值
* resolvedCallbacks 和 rejectedCallbacks 用于保存 then 中的回調躬翁,
* 因為當執(zhí)行完 Promise 時狀態(tài)可能還是等待中焦蘑,這時候應該把 then 中的回調保存起來用于狀態(tài)改變時使用
*/
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function myPromise(fn){
const that = this;
that.value = null;
that.status = PENDING; //默認狀態(tài)
that.fulfilledCallbacks = [];
that.rejectedCallbacks = [];
function resolve(value){
if(that.status === PENDING ){
}
}
function reject(value){
if(that.status === PENDING ){
}
}
// 執(zhí)行回調函數(shù)
try{
fn(resolve, reject)
}catch (e) {
reject(e);
}
}
于是思考當在resolve里該干些什么?resolve即執(zhí)行狀態(tài)盒发,首先status狀態(tài)值得變吧例嘱,改成fulfilled狀態(tài),同時將傳入的value值保存起來宁舰,以便下面的then會用到拼卵,最后得執(zhí)行回調里面的方法實現(xiàn)回調調用。reject同理蛮艰,于是按這個思路腋腮,就有了:
/**
* 創(chuàng)建三變量記錄表示狀態(tài)
* 用that保存this,避免后期閉包導致this的指向不對
* value 變量用于保存 resolve 或者 reject 中傳入的值
* resolvedCallbacks 和 rejectedCallbacks 用于保存 then 中的回調壤蚜,
* 因為當執(zhí)行完 Promise 時狀態(tài)可能還是等待中即寡,這時候應該把 then 中的回調保存起來用于狀態(tài)改變時使用
*/
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function myPromise(fn){
const that = this;
that.value = null;
that.status = PENDING; //默認狀態(tài)
that.fulfilledCallbacks = [];
that.rejectedCallbacks = [];
function resolve(value){
if(that.status === PENDING ){
that.status = FULFILLED;
that.value = value;
//執(zhí)行回調方法
that.fulfilledCallbacks.forEach(myFn => myFn(that.value))
}
}
function reject(value){
if(that.status === PENDING ){
that.status = REJECTED;
that.value = value;
//執(zhí)行回調方法
that.rejectedCallbacks.forEach(myFn => myFn(that.value))
}
}
// 執(zhí)行回調函數(shù)
try{
fn(resolve, reject)
}catch (e) {
reject(e);
}
}
于是promise的構造函數(shù)就簡陋的完成了,接下來實現(xiàn)then不就大工完成了嗎仍律?是不是有些小興奮~~~
promise中then的實現(xiàn)
考慮到所有的實例都要用到then方法嘿悬,在then得放在promise的原型鏈上实柠。當狀態(tài)是PENDING狀態(tài)時水泉,該做什么?不執(zhí)行回調窒盐,那就將回調方法分別放入不同的棧內草则,等待調用。當狀態(tài)為FULFILLED或者REJECTED時蟹漓,則執(zhí)行響應的方法即可炕横。于是:
myPromise.prototype.then = function (onFulfilled, onRejected){
let self = this;
//等待狀態(tài),則添加回調函數(shù)到棧中
if(self.status === PENDING){
self.fulfilledCallbacks.push(()=>{
onFulfilled(self.value);
});
self.rejectedCallbacks.push(()=>{
onRejected(self.value);
})
}
if(self.status === FULFILLED){
onFulfilled(self.value);
}
if(self.status === REJECTED){
onRejected(self.value)
}
}
于是一個簡單的promise實現(xiàn)如下:
/**
* 創(chuàng)建三變量記錄表示狀態(tài)
* 用that保存this葡粒,避免后期閉包導致this的指向不對
* value 變量用于保存 resolve 或者 reject 中傳入的值
* resolvedCallbacks 和 rejectedCallbacks 用于保存 then 中的回調份殿,
* 因為當執(zhí)行完 Promise 時狀態(tài)可能還是等待中膜钓,這時候應該把 then 中的回調保存起來用于狀態(tài)改變時使用
*/
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function myPromise(fn){
const that = this;
that.value = null;
that.status = PENDING; //默認狀態(tài)
that.fulfilledCallbacks = [];
that.rejectedCallbacks = [];
function resolve(value){
if(that.status === PENDING ){
that.status = FULFILLED;
that.value = value;
//執(zhí)行回調方法
that.fulfilledCallbacks.forEach(myFn => myFn(that.value))
}
}
function reject(value){
if(that.status === PENDING ){
that.status = REJECTED;
that.value = value;
//執(zhí)行回調方法
that.rejectedCallbacks.forEach(myFn => myFn(that.value))
}
}
// 執(zhí)行回調函數(shù)
try{
fn(resolve, reject)
}catch (e) {
reject(e);
}
}
myPromise.prototype.then = function (onFulfilled, onRejected){
let self = this;
//等待狀態(tài),則添加回調函數(shù)到棧中
if(self.status === PENDING){
self.fulfilledCallbacks.push(()=>{
onFulfilled(self.value);
});
self.rejectedCallbacks.push(()=>{
onRejected(self.value);
})
}
if(self.status === FULFILLED){
onFulfilled(self.value);
}
if(self.status === REJECTED){
onRejected(self.value)
}
}
let p = new myPromise((resolve, reject)=>{
console.log('hello');
resolve(5);
});
p.then((res)=>{
console.log(res);
})
p.then(()=>{
console.log('jj');
})
結果如下:
于是一個簡單的promise大工告成卿嘲!
可是有沒有發(fā)現(xiàn)then里并沒有返回一個promise,并不符合規(guī)范颂斜,所以可以對promise進行部分優(yōu)化。
改造promise
根據(jù)promise+規(guī)范改造promise拾枣,傳給then的應該是個promise,如下:
resolve 和reject的改造:
- 對于 resolve 函數(shù)來說沃疮,首先需要判斷傳入的值是否為 Promise 類型
- 為了保證函數(shù)執(zhí)行順序,需要將兩個函數(shù)體代碼使用 setTimeout 包裹起來
function resolve(value) {
if(value instanceof myPromise){
return value.then(resolve, reject);
}
setTimeout(()=>{
that.status = FULFILLED;
that.value = value;
//執(zhí)行會回調方法
that.fulfilledCallbacks.forEach(myFn => myFn(that.value))
},0)
}
function reject(value) {
setTimeout(()=>{
that.status = REJECTED;
that.value = value;
//執(zhí)行會回調方法
that.rejectedCallbacks.forEach(myFn => myFn(that.value))
}, 0);
}
接下來改造then方法:
首先我們需要新增一個變量 promise2梅肤,因為每個 then 函數(shù)都需要返回一個新的 Promise 對象司蔬,該變量用于保存新的返回對象,于是:
myPromise.prototype.then = function (onFulfilled, onRejected) {
let self = this;
let promise2 = null;
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : r => {throw r}
//等待狀態(tài)姨蝴,則添加回調函數(shù)到棧中
if(self.status === PENDING){
return (promise2 = new myPromise((resolve, reject)=>{
self.fulfilledCallbacks.push(()=>{
try {
let x = onFulfilled(self.value);
resolutionProduce(promise2, x, resolve, reject);
}catch (e) {
reject(e)
}
});
self.rejectedCallbacks.push(()=>{
try {
let x = onRejected(self.value);
resolutionProduce(promise2, x, resolve, reject);
}catch (e) {
reject(e);
}
onRejected(self.value);
});
}));
}
if(self.status === FULFILLED){
return(promise2 = new myPromise((resolve, reject)=>{
setTimeout(()=>{
try {
let x = onFulfilled(self.value);
resolutionProduce(promise2, x, resolve, reject);
}catch (e) {
reject(e);
}
}, 0)
}));
}
if(self.status === REJECTED){
return (promise2 = new myPromise((resolve, reject)=>{
setTimeout(()=>{
try {
let x = onRejected(self.value);
resolutionProduce(promise2, x, resolve, reject)
}catch (e) {
reject(e);
}
},0)
}));
}
}
思路跟之前基本一致俊啼,只是說把之前返回值改成promise,同時捕獲異常,在status狀態(tài)為FULFILLED或者REJECTED的時候執(zhí)行得加上異步setTimeout包裹左医。
接下來完成最核心的resolutionProduce函數(shù):
- 首先規(guī)范規(guī)定得保證當前的x不能與promise2一致吨些,否則將執(zhí)行無意義的相同操作,導致循環(huán)引用的發(fā)生炒辉。
例如:
let p = new myPromise((resolve, reject) => {
resolve(1)
})
let p1 = p.then(value => {
return p1
})
- 然后需要判斷 x 的類型
if (x instanceof MyPromise) {
x.then(function(value) {
resolutionProcedure(promise2, value, resolve, reject)
}, reject)
}
這里的代碼是完全按照規(guī)范實現(xiàn)的豪墅。如果 x 為 Promise 的話,需要判斷以下幾個情況:
- 如果 x 處于等待態(tài)黔寇,Promise 需保持為等待態(tài)直至 x 被執(zhí)行或拒絕
- 如果 x 處于其他狀態(tài)偶器,則用相同的值處理 Promise
function resolutionProduce(promise, x, resolve, reject){
if(promise === x){
return reject(new TypeError('Error'));
}
let called = false;
if(x !== null && (typeof x === 'object' || typeof x === 'function')){
try {
let then = x.then;
if(typeof then === 'function'){
then.call(x, y=>{
if(called) return;
called = true;
resolutionProduce(promise2, y, resolve, reject )
}, e =>{
if(e) return;
called = true;
reject(e);
})
}else {
resolve(x);
}
}catch (e) {
if(called) return;
called = true;
reject(e);
}
}else {
resolve(x);
}
}
- 首先創(chuàng)建一個變量 called 用于判斷是否已經(jīng)調用過函數(shù)
- 然后判斷 x 是否為對象或者函數(shù),如果都不是的話缝裤,將 x 傳入 resolve 中
- 如果 x 是對象或者函數(shù)的話屏轰,先把 x.then 賦值給 then,然后判斷 then 的類型憋飞,如果不是函數(shù)類型的話霎苗,就將 x 傳入 resolve 中
- 如果 then 是函數(shù)類型的話,就將 x 作為函數(shù)的作用域 this 調用之榛做,并且傳遞兩個回調函數(shù)作為參數(shù)唁盏,第一個參數(shù)叫做 resolvePromise ,第二個參數(shù)叫做
rejectPromise检眯,兩個回調函數(shù)都需要判斷是否已經(jīng)執(zhí)行過函數(shù)厘擂,然后進行相應的邏輯 - 以上代碼在執(zhí)行的過程中如果拋錯了,將錯誤傳入 reject 函數(shù)中
以上便是promise的簡單實現(xiàn)锰瘸,下回有時間將把catch與all刽严、race等方法實現(xiàn),敬請期待
未完待續(xù)避凝。舞萄。眨补。
參考: https://juejin.im/post/5b88e06451882542d733767a