簡介
學習之前 需要先對Promise有個基本了解哦,這里都默認大家都是比較熟悉Promise的
本次將帶小伙伴們實作Promise的基本功能
Promise的基本骨架Promise的thenPromise.then的多次呼叫then鏈式呼叫catch的實作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中需要傳遞一個回呼函式,他的引數中包含了resolve和rejected,呼叫resolve之后,狀態會變為fulfilled,呼叫rejected,狀態會變成rejected- 我定義了一個類,我們在
constructor中定義所需要的resolve和rejected函式,然后將這兩個函式傳入那個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方法中接受兩個引數,分別是onfulfilled和onrejected兩個函式,分別對應著狀態fulfilled和rejected- 這里要注意一個點我在
resolve和rejected中都使用了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 = https://www.cnblogs.com/codespirit-zx/archive/2022/03/08/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);
}
}
}
- 因為改進之后,需要存盤
resolve和rejected的value和reason值,所以我們定義了這兩個值 - 為了滿足多次呼叫,我們需要將
promise中的onfulfilled和onrejected改為陣列存盤以用來滿足我們的多次呼叫 - 定時器的問題我這邊說下,因為
setTimeout屬于宏任務,在同步代碼執行完畢之后,會接著執行微任務,所以宏任務是最后來執行的,所以也就造成了promise中的代碼執行完了,但是包裹在定時器中的then方法沒有獲取到結果 - 所以呢,在這里我決定讓處于定時器中的代碼直接執行而不壓入陣列中去,因為定時器之前的代碼已經執行完畢了,
promise的狀態也已經發生了改變,所以我就在then方法中判斷promise的狀態,如果是fulfilled和rejected狀態的話,傳過來的函式就直接執行
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 = https://www.cnblogs.com/codespirit-zx/archive/2022/03/08/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 = https://www.cnblogs.com/codespirit-zx/archive/2022/03/08/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 = https://www.cnblogs.com/codespirit-zx/archive/2022/03/08/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() });
}
}
- 大家可以自行進行測驗
本文來自博客園,作者:CodeSpirit,轉載請注明原文鏈接:https://www.cnblogs.com/codespirit-zx/p/15979455.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/440438.html
標籤:其他
上一篇:監聽頁面中的某個div的滾動事件,并將其滾動距離保存到cookie
下一篇:VUE實戰攻略
