概要
本文主要介紹了Promise上定義的api的特性,以及手寫如何實現這些特性。目的是把學習過程中的產出以博客的方式輸出,鞏固知識,也便於之后復習
博客思路
mdn上搜索Promise,了解類和api的定義:
- 定義了哪些屬性,分別代表什么含義
- api需要傳什么參數,返回什么值,可能拋出什么異常
- 看官方給出的用例,猜想內部可能的實現
- 編寫源碼,用官方用例驗證查看返回值是否一致
API的特性與手寫源碼
構造函數
- promise有狀態pending、rejected和resolved,所以應該有個變量來保存狀態
- 構造函數參數excutor是個同步執行的回調函數,函數執行的參數是兩個函數resolved和rejected,所以promise內部需要定義兩個函數,並且在構造行數中執行excutor的地方傳入
- .then中會傳入回調函數onResolved和onRejected,在resolved和rejected內會分別會觸發對應的回調函數,所以需要兩個數組保存then中傳進來的回調
- resolved和rejected只能執行一次,執行后promise的狀態會改變,且參數會傳遞給回調函數
- onRejected和onResolved異步執行
- excutor執行拋異常會直接執行rejected,所以excutor的執行需要catch錯誤
const PENDING = "PENDING";
const RESOLVED = "resolved";
const REJECTED = "rejected";
function MyPromise(excutor){
// promise內部保存着狀態值
this.status = PENDING;
this.data = null;
// then方法傳進來的回調函數此保存
this.onResolvedList = [];
this.onRejectedList = [];
let resolved = (value) => {
// resolved函數只能執行一次,所以先判斷狀態是不是pending
if(this.status !== PENDING){
return;
}
// 變更狀態為resolved
this.status = RESOLVED;
// 數據為傳進來的值
this.data = value;
// 判斷是否已經有onResolved回調已經穿入,有則異步執行
if(this.onResolvedList.length > 0){
setTimeout(() => {
this.onResolvedList.forEach(onResolved => {
onResolved(value);
});
}, 0);
}
}
let rejected = (reason) => {
if(this.status !== PENDING){
return
}
this.status = REJECTED;
this.data = reason;
if(this.onRejectedList.length > 0){
setTimeout(() => {
this.onRejectedList.forEach(onRejected => {
onRejected(reason);
});
});
}
}
try{
// 執行器函數同步執行,且參數為promise內定義的resolve和rejected
excutor(resolved, rejected);
}catch(error){
// 如果執行器函數出錯直接執行rejected
this.rejected(error);
}
}
then
- then會接受兩個回調函數onResolved和onRejected
- onResolved和onRejected會異步調用
- then會返回一個新的promise對象
- then的參數如果沒傳,那么value和reason繼續向下傳遞
- 如果執行then的時候,promise的狀態還是pending,那么只保存回調,並且確保回調執行后能修改新的promise的狀態
- 如果觸發的對應的回調函數執行拋異常,那么返回的新的回調函數狀態為rejected,reason則會catch到的error
- 如果觸發的對應回調函數執行返回值不是promise對象,那么返回新的promise狀態為resolved,value則為傳入then的回調的返回值
- 如果觸發的對應回調返回值是promise對象,那么新的promise返回值的狀態取決於改回調返回的promise
MyPromise.prototype.then = function(onResolved, onRejected){
// 如果沒有傳onResolved,則設置onResolved為返回value的函數
onResolved = typeof onResolved === "function" ? onResolved : value => value
// 如果沒有傳onRejected,則設置onRejected為拋處reason的函數
onRejected = typeof onRejected === "function" ? onRejected : reason => {throw reason}
return new MyPromise((resolved, rejected) => {
// 傳入要執行的回調函數
let callBackExcu = (callback) => {
try{
let result = callback(this.data);
if(result instanceof MyPromise){
// 如果回調返回值還是promise則then返回的promise的狀態取決於回調的返回的promise,成功就執行resolved失敗就執行rejected
result.then(resolved, rejected);
}else{
// 如果回調的返回值不為promise則新的promise狀態為resolved
resolved(result)
}
}catch(error){
// 如果回調執行拋處異常,則新的promise狀態為rejected
rejected(error);
}
}
if(this.status === PENDING){
// 如果狀態為pending則保存回調且確保回調執行后能修改當前返回的promise的狀態
this.onResolvedList.push((value) => {
callBackExcu(onResolved)
});
this.onRejectedList.push((reason) => {
callBackExcu(onRejected)
});
}else{
// 如果狀態不為pending則根據狀態執行對應的回調,且修改當前promise的狀態
switch(this.status){
case REJECTED:
// onRejected異步執行
setTimeout(() => {
callBackExcu(onRejected);
});
break;
case RESOLVED:
// onResolved異步執行
setTimeout(() => {
callBackExcu(onResolved);
});
break;
}
}
});
}
catch
catch和then其實差不多,不同點在於傳入的參數只有onRejected,所以
MyPromise.prototype.catch = function(onRejected){
// catch與then的不同點在於傳入的參數不一樣,不需要傳onResolve
return this.then(null, onRejected);
}
Promise.resolved
- resolved會返回一個promise對象
- 如果傳入的參數本就是一個primise對象則直接返回
- 如果是一個包含“then”方法的對象,返回新的promise對象,且狀態取決於then函數的執行,如果then的執行中拋錯,則新的promise狀態為rejected
- then的參數為兩個回調函數resolved和rejected
- 如果傳入參數value既不是promise的實例,也不是具備then函數的對象,則返回一個新的promise對象且改對象data就為value
MyPromise.resolve = function(value){
if(value instanceof MyPromise){
// 如果傳入的參數本就是一個primise對象則直接返回
return value;
}else if(typeof value.then === "function"){
return new MyPromise((resolved, rejected) => {
try{
// then的參數為兩個回調函數resolved和rejected
value.then(resolved, rejected);
}catch(error){
// 如果then的執行中拋錯,則新的promise狀態為rejected
rejected(error);
}
});
}else{
// 如果傳入參數value既不是promise的實例
return new MyPromise((resolved, rejected) => {
resolved(value);
});
}
}
Promise.rejected
- 接受參數reason,返回一個狀態為rejected的data為reason的promise實例
MyPromise.reject = function(reason){
return new MyPromise((resolved, rejected) => {
rejected(reason);
});
}
Promise.all
- 接收的參數是需要滿足可迭代協議,否則會拋錯
- 返回值是個promise
- 如果傳入的參數是個空的可迭代的對象,則返回一個狀態為resolved的可promise實例,data為空數組,
Promise.all([]) // Promise {<resolved>: Array(0)}
Promise.all("") // Promise {<resolved>: Array(0)}
- 如果傳入的參數中沒有promise實例,或者所有的promise已經是resolved狀態,則返回一個promise狀態為pending,且異步更新為resolved
let p = Promise.all([1,2,3,4,Promise.resolve(5)])
console.log(p); // Promise {<pending>}
- 如果存在promise且狀態還是pending,返回一個promise實例,且等所有promise都resolved后,狀態更新為resolved,data為傳入的順序
接下來看下源碼
// 先定義一個驗證參數是否滿足可迭代協議的方法
const isIterable = function(object){
return typeof object[Symbol.iterator] === "function"
&& typeof object[Symbol.iterator]() === "object"
&& typeof object[Symbol.iterator]().next === "function"
}
MyPromise.all = function(iterable){
if(!isIterable(iterable)){
// 不滿足可迭代協議拋錯
throw new TypeError("Object is not iterable");
}
let data = [];
let count = 0;
// 迭代參數生成數組
let params = Array.from(iterable);
return new MyPromise((resolved, rejected) => {
if(params.length === 0){
// 如果是空的可迭代對象,返回空數組
resolved(data);
}else{
params.forEach((element, index) => {
// 遍歷每個參數,統一處理成promise的實例
// 這樣就少了一個邏輯分支
let itemPromise = MyPromise.resolve(element);
itemPromise.then(
value => {
// data中的結果需要和傳入參數的順序一致
data[index] = value;
if(count === params.length - 1){
// 說明全都resolved了
resolved(data);
}
count++;
},
reason => {
// reject直接返回
rejected(reason);
}
);
});
}
});
}
Promise.race
- 接收一個可迭代對象,這點與方法"all"相同
- 返回一個新的promise
- 返回的promise狀態為pending,異步更新為resolved
let p = Promise.race([1,2,3,4]);
console.log(p); // Promise {<pending>}
p.then(
value => {
console.log(value); // Promise{<resolved>: 1}
}
);
- 傳入的若干promise中,只要有一個promise最先resolved或者rejected,則返回的promise狀態更新為resolved
let p1 = new Promise((resolved, rejected) => {
setTimeout(() => {
resolved("p1");
}, 10);
});
let p2 = new Promise((resolved, rejected) => {
setTimeout(() => {
resolved("p2");
}, 100);
});
let p = Promise.race([p2, p1])
p.then(
value => {
console.log(value); // p1
}
);
最后來看一下自己源碼的實現
MyPromise.race = function(iterable){
if(!isIterable(iterable)){
// 不滿足可迭代協議拋錯
throw new TypeError("Object is not iterable");
}
const params = Array.from(iterable);
return new MyPromise((resolved, rejected) => {
params.forEach((element, index) => {
const itemPromise = MyPromise.resolve(element);
itemPromise.then(
value => {
// 只要有一個promise resolved直接返回
resolved(value);
},
error => {
// 只要有一個promise rejected直接返回
rejected(error);
}
);
});
});
}