[手寫系列] 帶你實現一個簡單的Promise

CodeSpirit發表於2022-03-08

簡介

學習之前 需要先對Promise有個基本瞭解哦,這裡都預設大家都是比較熟悉Promise的

本次將帶小夥伴們實現Promise的基本功能

  1. Promise的基本骨架
  2. Promisethen
  3. Promise.then的多次呼叫
  4. then鏈式呼叫
  5. catch的實現
  6. finally的實現

01-搭建基本骨架

const PROMISE_STATUS_PENDING = "PROMISE_STATUS_PENDING";
const PROMISE_STATUS_FULFILLED = "PROMISE_STATUS_FULFILLED";
const PROMISE_STATUS_REJECTED = "PROMISE_STATUS_REJECTED";

class ZXPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING;
        const resolve = (value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_FULFILLED;
                console.log(value);
            }
        }
        const rejected = (reason) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_REJECTED;
                console.log(reason);
            }
        }
        executor(resolve, rejected)
    }
}

// 初步搭建好Promise的construtor結構
const promise = new ZXPromise((resolve, rejected) => {
    resolve("123");
    rejected("wushichu")
})
  • 因為Promise有三種狀態pending,fulfilled,rejected,我們這裡就宣告三個常量來代表這三種狀態
  • Promise中需要傳遞一個回撥函式,他的引數中包含了resolverejected,呼叫resolve之後,狀態會變為fulfilled,呼叫rejected,狀態會變成rejected
  • 我定義了一個類,我們在constructor中定義所需要的resolverejected函式,然後將這兩個函式傳入那個executor中去,這樣Promise的基本骨架就已經搭建完成了,非常簡單.

02-實現Promise的then功能

const PROMISE_STATUS_PENDING = "PROMISE_STATUS_PENDING";
const PROMISE_STATUS_FULFILLED = "PROMISE_STATUS_FULFILLED";
const PROMISE_STATUS_REJECTED = "PROMISE_STATUS_REJECTED";

class ZXPromise {
	constructor(executor) {
		this.status = PROMISE_STATUS_PENDING;
		const resolve = (value) => {
			if (this.status === PROMISE_STATUS_PENDING) {
				queueMicrotask(() => {
          //因為只有pending狀態才能進行變化
          if(this.status!==PROMISE_STATUS_PENDING) return
          this.status = PROMISE_STATUS_FULFILLED;
					if (this.onfufilled)
						this.onfufilled(value);
				})
			}
		}
		const rejected = (reason) => {
			if (this.status === PROMISE_STATUS_PENDING) {
				queueMicrotask(() => {
          if(this.status!==PROMISE_STATUS_PENDING) return
          this.status = PROMISE_STATUS_REJECTED;
					if (this.onrejected)
						this.onrejected(reason);
				})
			}
		}
		executor(resolve, rejected)
	}
	then(onfufilled, onrejected) {
		this.onfufilled = onfufilled;
		this.onrejected = onrejected;
	}
}

// 接下來開始寫then方法
const promise = new ZXPromise((resolve, rejected) => {
	resolve("123");
	rejected("wushichu");
})

promise.then((res) => {
	console.log("res", res);
}, (err) => {
	console.log("err", err);
})
  • then方法中接受兩個引數,分別是onfulfilledonrejected兩個函式,分別對應著狀態fulfilledrejected
  • 這裡要注意一個點我在resolverejected中都使用了queueMicrotask,這裡使用的目的是為了保證順序執行的一致性,確保在then方法執行過後,再去執行相關程式碼,這裡需要大家熟悉微任務佇列和巨集任務佇列,推薦大家看下這篇文章
    在JS中使用queueMicroTask

03-Promise.then多次呼叫

大家可以用上一部分的程式碼實驗一下,如果多次呼叫,會發現只有最後一個輸出,並且在定時器中使用,會出現結果為undefined

p1.then((res) => {
	console.log("res1", res);
});

p1.then((res) => {
	console.log('res2: ', res);
});

setTimeout(() => {
	p1.then((res) => {
		console.log("res4", res);
	})
}, 1000);

現在我們來解決下上述問題,看程式碼

const PROMISE_STATUS_PENDING = "PROMISE_STATUS_PENDING";
const PROMISE_STATUS_FULFILLED = "PROMISE_STATUS_FULFILLED";
const PROMISE_STATUS_REJECTED = "PROMISE_STATUS_REJECTED";

class ZXPromise {
	constructor(executor) {
		this.status = PROMISE_STATUS_PENDING;
		this.value = undefined;
		this.reason = undefined;
		this.onfufilled = [];
		this.onrejected = [];
		const resolve = (value) => {
			if (this.status === PROMISE_STATUS_PENDING) {
				queueMicrotask(() => {
					if (this.status !== PROMISE_STATUS_PENDING) return
					this.status = PROMISE_STATUS_FULFILLED;
					this.value = value;
					this.onfufilled.forEach(fn => {
						fn(value);
					});
				})
			}
		}
		const rejected = (reason) => {
			if (this.status === PROMISE_STATUS_PENDING) {
				queueMicrotask(() => {
					if (this.status !== PROMISE_STATUS_PENDING) return
					this.status = PROMISE_STATUS_REJECTED;
					this.reason = reason;
					this.onrejected.forEach(fn => {
						fn(reason);
					})
				})
			}
		}
		executor(resolve, rejected)
	}
	// 接下來為了Promise能夠多次呼叫 進行優化
	then(onfufilled, onrejected) {
		if (this.status === PROMISE_STATUS_FULFILLED) {
			onfufilled(this.value);
		}
		if (this.status === PROMISE_STATUS_REJECTED) {
			onrejected(this.value);
		}
		if (this.status === PROMISE_STATUS_PENDING) {
			this.onfufilled.push(onfufilled);
			this.onrejected.push(onrejected);
		}

	}
}
  • 因為改進之後,需要儲存resolverejectedvaluereason值,所以我們定義了這兩個值
  • 為了滿足多次呼叫,我們需要將promise中的onfulfilledonrejected改為陣列儲存以用來滿足我們的多次呼叫
  • 定時器的問題我這邊說下,因為setTimeout屬於巨集任務,在同步程式碼執行完畢之後,會接著執行微任務,所以巨集任務是最後來執行的,所以也就造成了promise中的程式碼執行完了,但是包裹在定時器中的then方法沒有獲取到結果
  • 所以呢,在這裡我決定讓處於定時器中的程式碼直接執行而不壓入陣列中去,因為定時器之前的程式碼已經執行完畢了,promise的狀態也已經發生了改變,所以我就在then方法中判斷promise的狀態,如果是fulfilledrejected狀態的話,傳過來的函式就直接執行

04-then方法的鏈式呼叫

要想實現鏈式呼叫,那麼then方法肯定是將Promise物件又給返回出來了,說到這了大家有沒有思路呢?

const PROMISE_STATUS_PENDING = "PROMISE_STATUS_PENDING";
const PROMISE_STATUS_FULFILLED = "PROMISE_STATUS_FULFILLED";
const PROMISE_STATUS_REJECTED = "PROMISE_STATUS_REJECTED";

class ZXPromise {
	constructor(executor) {
		this.status = PROMISE_STATUS_PENDING;
		this.value = undefined;
		this.reason = undefined;
		this.onfufilled = [];
		this.onrejected = [];
		const resolve = (value) => {
			if (this.status === PROMISE_STATUS_PENDING) {
				queueMicrotask(() => {
					if (this.status !== PROMISE_STATUS_PENDING) return
					this.status = PROMISE_STATUS_FULFILLED;
					this.value = value;
					this.onfufilled.forEach(fn => {
						fn(value);
					});
				})
			}
		}
		const rejected = (reason) => {
			if (this.status === PROMISE_STATUS_PENDING) {
				queueMicrotask(() => {
					if (this.status !== PROMISE_STATUS_PENDING) return
					this.status = PROMISE_STATUS_REJECTED;
					this.reason = reason;
					this.onrejected.forEach(fn => {
						fn(reason);
					})
				})
			}
		}
		try{
			executor(resolve, rejected)
		}catch(err){
			console.log(err);
		}
		
	}
	
	then(onfufilled, onrejected) {
		return new ZXPromise((resolve, rejected) => {
			if (this.status === PROMISE_STATUS_FULFILLED) {
				try {
					//如果then中有返回值,就會作為下一個then所接收的值
					const value = onfufilled(this.value);
					resolve(value);
				} catch (err) {
					rejected(err);
				}
				
			}
			if (this.status === PROMISE_STATUS_REJECTED) {
				try {
					const value = onrejected(this.value);
					resolve(value);
				} catch (err) {
					rejected(err);
				}
			}
			if (this.status === PROMISE_STATUS_PENDING) {
				try {
					this.onfufilled.push(() => {
						const value = onfufilled(this.value);
						resolve(value);
					});
				} catch (err) {
					rejected(err);
				}
				try {
					this.onrejected.push(() => {
						const value = onrejected(this.value);
						resolve(value);
					});
				} catch (err) {
					rejected(err);
				}
			}
		})
	}
}


const promise = new ZXPromise((resolve, rejected) => {
	resolve("123");
	rejected("wushichu");
})
promise.then((res) => {
	console.log("res1:", res);
	return "abc";
}, (err) => {
	console.log("err1", err);
}).then((res) => {
	console.log("res2", res);
}, (err) => {
	console.log("err2", err);
})
  • 變化最大的就是then方法了,大家可以看到我又把ZXPromise返回出去了,程式碼中我寫的很清楚了

05-catch方法實現

catch方法實際上是then第二個引數的語法糖,說到這裡大家有沒有明白什麼呢?

const PROMISE_STATUS_PENDING = "PROMISE_STATUS_PENDING";
const PROMISE_STATUS_FULFILLED = "PROMISE_STATUS_FULFILLED";
const PROMISE_STATUS_REJECTED = "PROMISE_STATUS_REJECTED";

const execFnWithCatchError = (execFn, value, resolve, reject) => {
    try {
        const result = execFn(value);
        resolve(result);
    } catch (err) {
        reject(err);
    }
}

class ZXPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING;
        this.value = undefined;
        this.reason = undefined;
        this.onfufilled = [];
        this.onrejected = [];
        const resolve = (value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    if (this.status !== PROMISE_STATUS_PENDING) return
                    this.status = PROMISE_STATUS_FULFILLED;
                    this.value = value;
                    this.onfufilled.forEach(fn => {
                        fn(value);
                    });
                })
            }
        }
        const rejected = (reason) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    if (this.status !== PROMISE_STATUS_PENDING) return
                    this.status = PROMISE_STATUS_REJECTED;
                    this.reason = reason;
                    this.onrejected.forEach(fn => {
                        fn(reason);
                    })
                    return this.reason;
                })
            }
        }
        executor(resolve, rejected)
    }

    then(onfufilled, onrejected) {
        //這一段是為了將錯誤程式碼傳遞下去的
        const defaultOnRejected = err => { throw err }
        onrejected = onrejected || defaultOnRejected
        return new ZXPromise((resolve, rejected) => {
            if (this.status === PROMISE_STATUS_FULFILLED && onfufilled) {
                execFnWithCatchError(onfufilled, this.value, resolve, rejected);
            }
            if (this.status === PROMISE_STATUS_REJECTED && onrejected) {
                execFnWithCatchError(onrejected, this.reason, resolve, rejected);
            }
            if (this.status === PROMISE_STATUS_PENDING) {
                if (onfufilled)
                    this.onfufilled.push(() => {
                        execFnWithCatchError(onfufilled, this.value, resolve, rejected);
                    });
                if (onrejected) {
                    this.onrejected.push(() => {
                        execFnWithCatchError(onrejected, this.reason, resolve, rejected);
                    });
                }
            }
        })
    }
    catch(onrejected) {
        return this.then(undefined, onrejected);
    }
}
  • 大家可以看到catch程式碼實際上就只有一行,就是將then方法進行了呼叫,是不是相當簡單呢
  • 然後我覺得那個try catch程式碼重複性比較高,所以我將它提取了出來複用
  • 然後大家看下那個then裡面的開頭,onrejected函式被給予了一個預設值,如果then沒有傳遞第二個引數,那麼會被賦予一個錯誤處理函式的預設值,丟擲錯誤後,會自動被try catch捕獲進行reject,這樣子錯誤會被層層傳遞,一直到最後被catch函式所執行.

06-finally的實現

finally就是要在最後執行的函式,無論什麼情況,實現起來也是非常簡單

    finally(fn) {
        return this.then(() => { fn() }, () => { fn() });
    }
  • 在類中加上這一段程式碼就好了,因為finally是無法接收任何resolve和rejected的值的,所以我們在傳遞的函式中執行fn,就是避免resolve的值和rejected的值被傳遞到finally上去

07-完整程式碼總覽

const PROMISE_STATUS_PENDING = "PROMISE_STATUS_PENDING";
const PROMISE_STATUS_FULFILLED = "PROMISE_STATUS_FULFILLED";
const PROMISE_STATUS_REJECTED = "PROMISE_STATUS_REJECTED";

const execFnWithCatchError = (execFn, value, resolve, reject) => {
    try {
        const result = execFn(value);
        resolve(result);
    } catch (err) {
        reject(err);
    }
}

class ZXPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING;
        this.value = undefined;
        this.reason = undefined;
        this.onfufilled = [];
        this.onrejected = [];
        const resolve = (value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    if (this.status !== PROMISE_STATUS_PENDING) return
                    this.status = PROMISE_STATUS_FULFILLED;
                    this.value = value;
                    this.onfufilled.forEach(fn => {
                        fn(value);
                    });
                })
            }
        }
        const rejected = (reason) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    if (this.status !== PROMISE_STATUS_PENDING) return
                    this.status = PROMISE_STATUS_REJECTED;
                    this.reason = reason;
                    this.onrejected.forEach(fn => {
                        fn(reason);
                    })
                    return this.reason;
                })
            }
        }
        executor(resolve, rejected)
    }

    then(onfufilled, onrejected) {
        //這一段是為了將錯誤程式碼傳遞下去的
        const defaultOnRejected = err => { throw err }
        onrejected = onrejected || defaultOnRejected
        return new ZXPromise((resolve, rejected) => {
            if (this.status === PROMISE_STATUS_FULFILLED && onfufilled) {
                execFnWithCatchError(onfufilled, this.value, resolve, rejected);
            }
            if (this.status === PROMISE_STATUS_REJECTED && onrejected) {
                execFnWithCatchError(onrejected, this.reason, resolve, rejected);
            }
            if (this.status === PROMISE_STATUS_PENDING) {
                if (onfufilled)
                    this.onfufilled.push(() => {
                        execFnWithCatchError(onfufilled, this.value, resolve, rejected);
                    });
                if (onrejected) {
                    this.onrejected.push(() => {
                        execFnWithCatchError(onrejected, this.reason, resolve, rejected);
                    });
                }
            }
        })
    }
    catch(onrejected) {
        return this.then(undefined, onrejected);
    }

    finally(fn) {
        return this.then(() => { fn() }, () => { fn() });
    }
}
  • 大家可以自行進行測試

相關文章