結(jié)構(gòu)
promise經(jīng)常使用的方法類似下邊這樣:
var promise1 = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('foo');
}, 300);
});
promise1.then(function(value) {
console.log(value);
// expected output: "foo"
});
從這個例子看來promise是作為構(gòu)造函數(shù)使用,參數(shù)是一個function(暫且稱為executor),通過調(diào)用then設(shè)置成功或者失敗的回調(diào)函數(shù),所以MyPromise的基本結(jié)構(gòu)如下:
const MyPromise = function (executor) {
}
MyPromise.prototype.then=function () {}
模擬promise基本功能
繼續(xù)上邊的例子,我們來看看promise主要的工作流程:
var promise1 = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('foo');
}, 300);
});
promise1.then(function(value) {
console.log(value);
// expected output: "foo"
});
從上邊例子可以看出promise通過then方法注冊成功和失敗的回調(diào)函數(shù),然后在executor中通過resolve和reject給成功和失敗的回調(diào)函數(shù)傳遞參數(shù)评抚,然后調(diào)用。
下邊就是模擬promsie的代碼:
STATUS = {
SUCCESS: 'SUCCESS',
FAIL: 'FAIL',
PENDING: 'PENDING'
};
const MyPromise = function (executor) {
this.status = STATUS.PENDING; // 存儲當(dāng)前promise狀態(tài)
this.onFulfilled = Function.prototype; // 存儲成功的回調(diào)函數(shù)
this.onRejected = Function.prototype; // 存儲失敗的回調(diào)函數(shù)
this.value = '';
this.error = '';
const resolve = (value) => {
if (value instanceof MyPromise) {
return MyPromise.then(resolve, reject)
}
setTimeout(() => {
if (this.status === STATUS.PENDING) {
this.status = STATUS.SUCCESS;
this.value = value;
this.onFulfilled(this.value);
}
}, 20)
};
const reject = (error) => {
if (error instanceof MyPromise) {
return MyPromise.then(resolve, reject)
}
setTimeout(() => {
if (this.status === STATUS.PENDING) {
this.status = STATUS.FAIL;
this.error = error;
this.onRejected(this.error);
}
}, 20)
};
try {
executor(resolve, reject)
} catch(e) {
reject(e)
}
};
MyPromise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (data) => data;
onRejected = typeof onRejected === 'function' ? onRejected : (error) => { throw error};
if (this.status === STATUS.SUCCESS) {
this.onFulfilled(this.value)
} else if (this.status === STATUS.FAIL) {
this.onRejected(this.error);
} else if (this.status === STATUS.PENDING) {
this.onFulfilled = onFulfilled;
this.onRejected = onRejected;
}
};
在構(gòu)造函數(shù)中設(shè)置resolve和reject方法伯复,當(dāng)調(diào)用這兩個方法會將參數(shù)存在對象中供成功或者失敗回調(diào)函數(shù)調(diào)用慨代,resolve會將值存在value中,reject會將值存在error中啸如,然后調(diào)用對應(yīng)狀態(tài)的回調(diào)函數(shù)侍匙。
在then方法中進行判斷:
- 如果promise處于pending狀態(tài)就給promise注冊回調(diào)函數(shù)
- 如果成功或者失敗則直接調(diào)用回調(diào)函數(shù)將promise中的value或者error傳過去。
這塊注意點:
- resolve和reject函數(shù)中使用了setTimeout叮雳,作用是確保調(diào)用的時候回調(diào)函數(shù)已經(jīng)掛載在對象上了
- resolve和reject函數(shù)中加入了狀態(tài)判斷是因為promise在狀態(tài)一旦進入失敗或者成功后狀態(tài)
就不可更改了
想暗。
完善promise多次掛載
promise允許多次掛載例如下邊這種情況:
let promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('data')
}, 2000)
})
promise.then(data => {
console.log(`1: ${data}`)
})
promise.then(data => {
console.log(`2: ${data}`)
})
promise通過then方法掛載了兩個成功回調(diào)函數(shù),按照上邊的方法并不能完成這個需求帘不,所以加了一些修改:
STATUS = {
SUCCESS: 'SUCCESS',
FAIL: 'FAIL',
PENDING: 'PENDING'
};
const MyPromise = function (executor) {
this.status = STATUS.PENDING;
// 將回調(diào)函數(shù)改為一個數(shù)組
this.onFulfilledArray = [];
this.onRejectedArray = [];
this.value = '';
this.error = '';
const resolve = (value) => {
if (value instanceof MyPromise) {
return MyPromise.then(resolve, reject)
}
setTimeout(() => {
if (this.status === STATUS.PENDING) {
this.status = STATUS.SUCCESS;
this.value = value;
this.onFulfilledArray.forEach((func) => {
func(this.value);
});
}
}, 20)
};
const reject = (error) => {
if (error instanceof MyPromise) {
return MyPromise.then(resolve, reject)
}
setTimeout(() => {
if (this.status === STATUS.PENDING) {
this.status = STATUS.FAIL;
this.error = error;
this.onRejectedArray.forEach((func) => {
func(this.error);
});
}
}, 20)
};
try {
executor(resolve, reject)
} catch(e) {
reject(e)
}
};
MyPromise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (data) => data;
onRejected = typeof onRejected === 'function' ? onRejected : (error) => { throw error};
if (this.status === STATUS.SUCCESS) {
onFulfilled(this.value);
} else if (this.status === STATUS.FAIL) {
onRejected(this.error);
}else if (this.status === STATUS.PENDING) {
this.onFulfilledArray.push(onFulfilled);
this.onRejectedArray.push(onRejected);
}
};
promise去掉成功或者失敗回調(diào)函數(shù)存儲说莫,改為使用數(shù)組存儲成功和失敗的回調(diào),然后寞焙,在then函數(shù)中如果promise狀態(tài)是pending就將回調(diào)函數(shù)加入數(shù)組中存儲下储狭,接著修改了resolve和reject方法告唆,不是直接調(diào)用回調(diào)函數(shù)然后穿參而是遍歷回調(diào)函數(shù)數(shù)組依次執(zhí)行。
鏈?zhǔn)秸{(diào)用
promise的then是支持鏈?zhǔn)秸{(diào)用的晶密,then方法依然會返回promise,就像下邊這樣:
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('lucas')
}, 2000)
})
promise.then(data => {
console.log(data)
return `${data} next then`
})
.then(data => {
console.log(data) // lucas next then
})
因此代碼還需要進一步完善,then要返回promise模她,then方法修改為如下:
MyPromise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (data) => data;
onRejected = typeof onRejected === 'function' ? onRejected : (error) =>{ throw error};
let promise = null;
if (this.status === STATUS.SUCCESS) {
promise = new MyPromise((resolve, reject) => {
setTimeout(() => { // 保持resolve操作在then后邊
try {
resolve(onFulfilled(this.value));
} catch (e) {
reject(e);
}
})
});
return promise;
} else if (this.status === STATUS.FAIL) {
promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
resolve(onRejected(this.error));
} catch (e) {
reject(e);
}
})
});
return promise;
} else if (this.status === STATUS.PENDING) {
promise = new MyPromise((resolve, reject) => {
this.onFulfilledArray.push((value) => {
try {
resolve(onFulfilled(value));
} catch (e) {
reject(e);
}
});
this.onRejectedArray.push((error) => {
try {
reject(onRejected(error));
} catch (e) {
reject(e);
}
});
});
return promise;
}
};
難點在于pending狀態(tài)下的處理稻艰,這里使用了閉包的思想,在給回調(diào)函數(shù)緩存數(shù)組中存入回調(diào)函數(shù)的時候侈净,閉包會存下promise返回值的resolve和reject方法與then方法的兩個回調(diào)函數(shù)尊勿,這樣在promise的resolve與reject方法調(diào)用回調(diào)函數(shù)的時候,會從閉包中取出對應(yīng)的內(nèi)容畜侦,完成功能元扔。
鏈?zhǔn)秸{(diào)用處理then方法直接返回promise的情況
上邊的例子中沒有處理then方法回調(diào)函數(shù)如果直接返回一個promise的情況,例子如下:
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('lucas')
}, 2000)
})
promise.then(data => {
console.log(data)
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`${data} next then`)
}, 4000)
})
})
.then(data => {
console.log(data)
})
這種情況在promise中會解構(gòu)promise返回旋膳,所以上邊代碼需要進行處理澎语,加入對于返回值是promise的解構(gòu)處理。
then方法修改為下邊這樣:
MyPromise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (data) => data;
onRejected = typeof onRejected === 'function' ? onRejected : (error) => { throw error};
let promise = null;
if (this.status === STATUS.SUCCESS) {
promise = new MyPromise((resolve, reject) => {
setTimeout(() => { // 保持resolve操作在then后邊
try {
resolvePromiseResult(promise, onFulfilled(this.value), resolve, reject);
} catch (e) {
reject(e);
}
})
});
return promise;
} else if (this.status === STATUS.FAIL) {
promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
try {
resolvePromiseResult(promise, onRejected(this.error), resolve, reject);
} catch (e) {
reject(e);
}
})
});
return promise;
} else if (this.status === STATUS.PENDING) {
promise = new MyPromise((resolve, reject) => {
this.onFulfilledArray.push((value) => {
try {
resolvePromiseResult(promise, onFulfilled(value), resolve, reject);
} catch (e) {
reject(e);
}
});
this.onRejectedArray.push((error) => {
try {
resolvePromiseResult(promise, onRejected(error), resolve, reject);
} catch (e) {
reject(e);
}
});
});
return promise;
}
};
這里添加了一個方法將promise和返回值與resolve和reject作為參數(shù)傳遞了進去验懊,在方法內(nèi)部進行處理擅羞。
添加方法的完整代碼如下:
function resolvePromiseResult(promise, result, resolve, reject) {
if (result instanceof MyPromise) {
if (result.status === STATUS.PENDING) {
result.then((data) => {
resolvePromiseResult(promise, data, resolve, reject);
}, reject)
} else {
result.then(resolve, reject);
}
return;
}
const isComplexObject = (typeof result === 'function' || typeof result === 'object') && result !== null;
if (isComplexObject) {
try {
const thenable = result.then;
if (typeof thenable === 'function') {
thenable.call(result, (data) => {
return resolvePromiseResult(promise, data, resolve, reject);
}, (error) => {
return reject(error);
})
} else {
resolve(result);
}
} catch (e) {
return reject(e);
}
} else {
resolve(result);
}
}
代碼重點如下:
- 首先對與result是promise進行處理,當(dāng)promise是pending狀態(tài)义图,進行遞歸調(diào)用减俏,如果是success后者fail狀態(tài)直接改變promise狀態(tài)傳遞給resolve與reject函數(shù)回調(diào)。
2.針對result如果是類promise處理碱工,與promise對象一樣的處理方式
promise 靜態(tài)方法實現(xiàn)
catch
catch方法很簡單
MyPromise.prototype.catch = function(catchFunc) {
return this.then(null, catchFunc)
}
成功的情況我們不處理只處理錯誤的情況
Promise.resolve與Promise.reject模擬
resolve與reject靜態(tài)方法也很簡單就是返回一個給定值的promise
MyPromise.resolve = function (value) {
return new MyPromise((resolve, reject) => {
resolve(value)
})
};
MyPromise.reject = function (value) {
return new MyPromise((resolve, reject) => {
reject(value)
})
};
Promise.all與 Promise.race模擬
promise的all方法是用來處理多個promise的方法娃承,參數(shù)是個promise數(shù)組,當(dāng)所有promise完成后會返回一個數(shù)組怕篷,存儲每個promise的返回結(jié)果历筝,模擬代碼也很簡單,如下:
MyPromise.all = function (promiseArray) {
if (!Array.isArray(promiseArray)) {
throw new Error('The arguments should be an array!')
}
return new MyPromise((resolve, reject) => {
try {
let resultArray = [];
const length = promiseArray.length;
for (let i = 0; i < length; i++) {
promiseArray[i].then(data => {
resultArray.push(data);
if (resultArray.length === length) {
resolve(resultArray)
}
}, reject)
}
} catch (e) {
reject(e)
}
}
)
}
race方法是當(dāng)?shù)谝粋€promsie返回后就返回結(jié)果廊谓,參數(shù)也是一個promise數(shù)組漫谷,實現(xiàn)方法也非常簡單,代碼如下:
MyPromise.race = function (promiseArray) {
if (!Array.isArray(promiseArray)) {
throw new TypeError('The arguments should be an array!')
}
return new MyPromise((resolve, reject) => {
try {
const length = promiseArray.length
for (let i = 0; i < length; i++) {
promiseArray[i].then(resolve, reject)
}
} catch (e) {
reject(e)
}
})
}
道理也很簡單就是在第一個promsie響應(yīng)的時候就返回結(jié)果終止訂閱蹂析。
結(jié)束語
本篇文章嘗試模擬一個promise舔示,借此來深入理解下這個js常用且很重要的功能,分享出來希望可以幫助和我一樣的前端小白电抚,很多地方實現(xiàn)可能不夠嚴(yán)謹(jǐn)惕稻,或者可能存在錯誤,歡迎大家指正蝙叛。