之前寫過一篇關于promise的一些常規用法,以及promise與async/await關系的文章。但是我們知道,要想把一個知識點完全掌握,知其然而不知其所以然是遠遠不夠的,那么接下來將要來探討promise的原理,來分析分析promise的這種規則機制是如何實現的。我們通過手寫promise的方式達到這個目的,同時來加深對primise的理解。
思路
手寫promise
之前,我們先來回憶一下promise
的使用方式,便于整理思路。
const p1 = new Promise((resolve, reject)=>{
console.log('此處同步執行');
const a = 2;
setTimeout(()=>{
// 此處異步執行
if(a>0){
resolve('成功');
}else{
reject('失敗原因');
}
},1000)
});
p1.then((res)=>{
console.log(res);
},(err)=>{
console.log(err);
})
從上面的代碼可以看出,要想使用promise
需要先通過new Promise(fn)
生成一個實例p1
,然后通過實例p1
調用then()
和catch()
方法。因此可以得出以下幾點結論:
-
Promise
本身是一個構造函數,其參數fn
是一個同步執行的回調函數,該函數執行的參數也是兩個函數resolve
和reject
。這兩個參數的作用是等異步操作執行完成后,為后續方法的執行傳參,如:then()
和catch()
。 -
then()
用兩個函數作為參數,在實例p1
中的resolve
和reject
方法中分別觸發對應的函數,并把異步操作執行的結果傳遞給對應的函數。 -
Promise
有三種狀態:pending
、rejected
和resolved
,同步回調函數fn
開始執行時狀態為pending
,執行了resolve
和reject
后會將其狀態改為resolved
和rejected
。resolved和rejected只能執行一次,且Promise
狀態一旦被確定下來,那么就是不可更改的(鎖定)。
通過觀察Promise
的使用方式得出的幾點結論,書寫promise的思路大致可以通過下面幾個方面來完成:
- 定義
Promise
的三種狀態; - 創建構造函數,并為構造函數定義一個回調函數作為參數;
- 在構造函數內定義變量來保存
Promise
的狀態,定義兩個函數resolve
和reject
,并在構造函數中執行回調函數的時候將此傳入; - 函數
resolve
和reject
目前的作用是改變Promise
的狀態,保存異步操作返回的值或者失敗的原因; - 為構造函數創建
then()
方法,then()
方法的參數是兩個函數onResolved
、onRejected
,這兩個函數將被傳入構造函數內定義的resolve
和reject
方法中執行。此時函數resolve
和reject
發揮了它的第二個作用,就是執行then()
方法傳遞過來的回調函數。
實現
有了大致的思路,那么接下來就是如何去實現它。
- Promise構造函數的設計,對應思路1、2、3、4
const PROMISE_STATUS_PENDING = 'pending';
const PROMISE_STATUS_FULFILLED = 'fulfilled';
const PROMISE_STATUS_REJECTED = 'rejected';
class myPromise {
// * 記錄狀態
constructor(executor) {
// * 保存Promise的狀態
this.status = PROMISE_STATUS_PENDING;
// * 保存傳入的值
this.value = undefined;
this.reason = undefined;
const resolve = value => {
if (this.status == PROMISE_STATUS_PENDING) {
this.status = PROMISE_STATUS_FULFILLED;
this.value = value;
console.log('resolve被調用');
}
};
const reject = reason => {
if (this.status == PROMISE_STATUS_PENDING) {
this.status = PROMISE_STATUS_REJECTED;
this.reason = reason;
console.log('reject被調用');
}
};
executor(resolve, reject);
}
}
- 定義好構造函數,接下來的任務就是書寫構造函數的方法了,對應5。修改上面的代碼如下:
const PROMISE_STATUS_PENDING = 'pending';
const PROMISE_STATUS_FULFILLED = 'fulfilled';
const PROMISE_STATUS_REJECTED = 'rejected';
class myPromise {
// * 記錄狀態
constructor(executor) {
// * 保存Promise的狀態
this.status = PROMISE_STATUS_PENDING;
// * 保存傳入的值
this.value = undefined;
this.reason = undefined;
const resolve = value => {
if (this.status == PROMISE_STATUS_PENDING) {
this.status = PROMISE_STATUS_FULFILLED;
//* 定時器是一個宏任務,會放在下一次事件循環時使用
queueMicrotask(() => {
this.value = value;
console.log('resolve被調用', this.value);
// * 執行then傳入進來的第一個回調函數
this.onResolved(this.value);
});
}
};
const reject = reason => {
if (this.status == PROMISE_STATUS_PENDING) {
this.status = PROMISE_STATUS_REJECTED;
queueMicrotask(() => {
this.reason = reason;
console.log('reject被調用', this.reason);
// * 執行then傳入進來的第二個回調函數
this.onRejected(this.reason);
});
}
};
executor(resolve, reject);
}
// then方法
then(onResolved, onRejected) {
this.onResolved = onResolved;
this.onRejected = onRejected;
}
}
優化
完成以上代碼已經搭建了一個具備基本功能的Promise
,不防試一下,相信它會帶給你滿意的結果。
const promise = new myPromise((resolve, reject) => {
console.log('狀態pending');
resolve('1111');
});
promise.then(
res => {
console.log('res:', res);
},
err => {
console.log('err:', err);
},
);
運行以上代碼,命令行會相繼輸出狀態pending
、resolve被調用 1111
、res: 1111
等,這代表著最基礎版的Promise
已經完成了。但是它仍然有很多問題,比如then()
方法無法多次調用和鏈式調用、沒有catch()
方法等,所以接下來我們就要優化上面基礎版的Promise
,使它具備和官方基本一致的功能。
- 實現
then()
的多次調用
then()
多次調用就需要在構造函數里定義兩個數組保存then()
方法中傳進來的回調,然后遍歷這個數組,執行數組里的所有回調函數,修改代碼如下:
const PROMISE_STATUS_PENDING = 'pending';
const PROMISE_STATUS_FULFILLED = 'fulfilled';
const PROMISE_STATUS_REJECTED = 'rejected';
class myPromise {
// * 記錄狀態
constructor(executor) {
// * 保存Promise的狀態
this.status = PROMISE_STATUS_PENDING;
// * 保存傳入的值
this.value = undefined;
this.reason = undefined;
this.onResolvedFns = [];
this.onRejectedFns = [];
const resolve = value => {
if (this.status == PROMISE_STATUS_PENDING) {
//* 定時器是一個宏任務,會放在下一次事件循環時使用
queueMicrotask(() => {
this.status = PROMISE_STATUS_FULFILLED;
this.value = value;
console.log('resolve被調用', this.value);
// * 執行then傳入進來的第一個回調函數
this.onResolvedFns.forEach(Fn => {
Fn(this.value);
});
});
}
};
const reject = reason => {
if (this.status == PROMISE_STATUS_PENDING) {
queueMicrotask(() => {
this.status = PROMISE_STATUS_REJECTED;
this.reason = reason;
console.log('reject被調用', this.reason);
// * 執行then傳入進來的第二個回調函數
this.onRejectedFns.forEach(Fn => {
Fn(this.reason);
});
});
}
};
executor(resolve, reject);
}
// then方法
then(onResolved, onRejected) {
console.log(this.status);
// 如果then方法調用的時候,狀態已經確定下來了,應該直接執行的
if (this.status === PROMISE_STATUS_FULFILLED && onResolved) {
onResolved(this.value);
} else if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
onRejected(this.reason);
} else {
// 將成功回調和失敗回調添加到數組中
this.onResolvedFns.push(onResolved);
this.onRejectedFns.push(onRejected);
}
}
}
- 實現
then()
的鏈式調用
從上面的代碼中可以清楚的看到then()
方法是掛載在構造函數myPromise
上的,所以為了實現鏈式調用,需要在then()
方法里返回一個新的Promise
對象,然后使用新的Promise
對象的resolve
方法去處理對應的回調函數的返回值。從代碼的簡潔度考慮,我們需要封裝一個工具函數,用來處理異常和回調函數。
與此同時,當回調函數executor
的執行發生異常時,也許有執行reject
|函數。因此,我們把代碼調整如下:
const PROMISE_STATUS_PENDING = 'pending';
const PROMISE_STATUS_FULFILLED = 'fulfilled';
const PROMISE_STATUS_REJECTED = 'rejected';
// 工具函數
function execFunctionWithCatchError(exeFn, value, resolve, reject) {
try {
let result = exeFn(value);
resolve(result);
} catch (err) {
reject(err);
}
}
class myPromise {
// * 記錄狀態
constructor(executor) {
// * 保存Promise的狀態
this.status = PROMISE_STATUS_PENDING;
// * 保存傳入的值
this.value = undefined;
this.reason = undefined;
this.onResolvedFns = [];
this.onRejectedFns = [];
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;
// console.log('resolve被調用', this.value);
// * 執行then傳入進來的第一個回調函數
this.onResolvedFns.forEach(Fn => {
Fn(this.value);
});
});
}
};
const reject = reason => {
if (this.status == PROMISE_STATUS_PENDING) {
// * 添加微任務
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_REJECTED;
this.reason = reason;
// console.log('reject被調用', this.reason);
// * 執行then傳入進來的第二個回調函數
this.onRejectedFns.forEach(Fn => {
Fn(this.reason);
});
});
}
};
// * 在調用executor時判斷里面是否拋出異常
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
// then方法
then(onResolved, onRejected) {
return new myPromise((resolve, reject) => {
// 如果then方法調用的時候,狀態已經確定下來了,應該直接執行的
if (this.status === PROMISE_STATUS_FULFILLED && onResolved) {
// onResolved(this.value);
execFunctionWithCatchError(onResolved, this.value, resolve, reject);
} else if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
// onRejected(this.reason);
execFunctionWithCatchError(
onRejected,
this.reason,
resolve,
reject,
);
} else {
// 將成功回調和失敗回調添加到數組中
this.onResolvedFns.push(() => {
execFunctionWithCatchError(
onResolved,
this.value,
resolve,
reject,
);
});
this.onRejectedFns.push(() => {
execFunctionWithCatchError(
onRejected,
this.reason,
resolve,
reject,
);
});
}
});
}
}
-
catch()
方法的實現
我們知道,在官方提供的Promise
中可不止then()
一種方法,其中最常用的便是catch()
方法了。
catch()
與then()
方法不同,它只會接受一個onRejected
的回調函數,catch()
方法執行的其實是then()
方法第二個參數的工作,then(null, function() {})
就等同于catch(function() {})
。但是用this.then(undefined,onRejected);
來實現catch()
方法顯然是不可以的,因為這樣做的話,catch()
方法是針對新的Promise
的rejected
的狀態,我們要解決的問題就是如何讓catch()
方法捕獲原Promise
對象的rejected
狀態。
所以我們要對then()
方法做一些改動,在方法內部前面判斷第二個參數是否有值,如果沒有值,就重新賦值為一個函數,函數內部拋出一個異常。這樣在新的Promise
就能捕獲到原來的promise
的rejected
的狀態了。具體實現方式如下:
const PROMISE_STATUS_PENDING = 'pending';
const PROMISE_STATUS_FULFILLED = 'fulfilled';
const PROMISE_STATUS_REJECTED = 'rejected';
// 工具函數
function execFunctionWithCatchError(exeFn, value, resolve, reject) {
try {
let result = exeFn(value);
resolve(result);
} catch (err) {
reject(err);
}
}
class myPromise {
// * 記錄狀態
constructor(executor) {
// * 保存Promise的狀態
this.status = PROMISE_STATUS_PENDING;
// * 保存傳入的值
this.value = undefined;
this.reason = undefined;
this.onResolvedFns = [];
this.onRejectedFns = [];
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;
// console.log('resolve被調用', this.value);
// * 執行then傳入進來的第一個回調函數
this.onResolvedFns.forEach(Fn => {
Fn(this.value);
});
});
}
};
const reject = reason => {
if (this.status == PROMISE_STATUS_PENDING) {
// * 添加微任務
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_REJECTED;
this.reason = reason;
// console.log('reject被調用', this.reason);
// * 執行then傳入進來的第二個回調函數
this.onRejectedFns.forEach(Fn => {
Fn(this.reason);
});
});
}
};
// * 在調用executor時判斷里面是否拋出異常
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
// then方法
then(onResolved, onRejected) {
onRejected =
onRejected ||
(err => {
throw err;
});
return new myPromise((resolve, reject) => {
// 如果then方法調用的時候,狀態已經確定下來了,應該直接執行的
if (this.status === PROMISE_STATUS_FULFILLED && onResolved) {
// onResolved(this.value);
execFunctionWithCatchError(onResolved, this.value, resolve, reject);
} else if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
// onRejected(this.reason);
execFunctionWithCatchError(
onRejected,
this.reason,
resolve,
reject,
);
} else {
// 將成功回調和失敗回調添加到數組中
if (onResolved)
this.onResolvedFns.push(() => {
execFunctionWithCatchError(
onResolved,
this.value,
resolve,
reject,
);
});
if (onRejected)
this.onRejectedFns.push(() => {
execFunctionWithCatchError(
onRejected,
this.reason,
resolve,
reject,
);
});
}
});
}
// * catch方法
catch(onRejected) {
this.then(undefined, onRejected);
}
}
-
finally()
方法的實現
前面講到的then()
和catch()
都是Promise
的實例方法,也可以稱為對象方法,除此之外,Promise
還有一個實例方法,那就是finally()
。finally()
方法大致可以概括如下:
-
finally
是ES9(ES2018)新增的一個特性:表示無論Promise
的狀態變為resolved
還是reject
,最終都會被執行的代碼。 -
finally
方法是不接收參數的,因為無論前面是resolved
狀態,還是reject
狀態,它都會執行。 -
finally
其實也是返回一個Promise對象,但是其實很少人會用它。
因此,finally
方法的實現也很簡單,只需要在Promise
構造函數中定義一個方法,無論promise
的狀態是什么。代碼實現如下:
const PROMISE_STATUS_PENDING = 'pending';
const PROMISE_STATUS_FULFILLED = 'fulfilled';
const PROMISE_STATUS_REJECTED = 'rejected';
// 工具函數
function execFunctionWithCatchError(exeFn, value, resolve, reject) {
try {
let result = exeFn(value);
resolve(result);
} catch (err) {
reject(err);
}
}
class myPromise {
// * 記錄狀態
constructor(executor) {
// * 保存Promise的狀態
this.status = PROMISE_STATUS_PENDING;
// * 保存傳入的值
this.value = undefined;
this.reason = undefined;
this.onResolvedFns = [];
this.onRejectedFns = [];
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;
// console.log('resolve被調用', this.value);
// * 執行then傳入進來的第一個回調函數
this.onResolvedFns.forEach(Fn => {
Fn(this.value);
});
});
}
};
const reject = reason => {
if (this.status == PROMISE_STATUS_PENDING) {
// * 添加微任務
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_REJECTED;
this.reason = reason;
// console.log('reject被調用', this.reason);
// * 執行then傳入進來的第二個回調函數
this.onRejectedFns.forEach(Fn => {
Fn(this.reason);
});
});
}
};
// * 在調用executor時判斷里面是否拋出異常
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
// then方法
then(onResolved, onRejected) {
onRejected =
onRejected ||
(err => {
throw err;
});
return new myPromise((resolve, reject) => {
// 如果then方法調用的時候,狀態已經確定下來了,應該直接執行的
if (this.status === PROMISE_STATUS_FULFILLED && onResolved) {
// onResolved(this.value);
execFunctionWithCatchError(onResolved, this.value, resolve, reject);
} else if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
// onRejected(this.reason);
execFunctionWithCatchError(
onRejected,
this.reason,
resolve,
reject,
);
} else {
// 將成功回調和失敗回調添加到數組中
if (onResolved)
this.onResolvedFns.push(() => {
execFunctionWithCatchError(
onResolved,
this.value,
resolve,
reject,
);
});
if (onRejected)
this.onRejectedFns.push(() => {
execFunctionWithCatchError(
onRejected,
this.reason,
resolve,
reject,
);
});
}
});
}
// * catch方法
catch(onRejected) {
this.then(undefined, onRejected);
}
// finally方法
finally(onFinally) {
return this.then(
() => {
onFinally();
},
() => {
onFinally();
},
);
}
}
-
Promise
的類方法的實現
前面說到的then
、catch
、finally
方法都屬于Promise
的實例方法,都是存放在Promise
的prototype
上的。下面我們將學習類方法。
-
Promise.resolve()
方法:有時候我們已經有一個現成的內容,希望將其轉成Promise
來使用,這個時候我們可以使用Promise.resolve()
方法來完成,所以Promise.resolve()
相當于new Promise
,并且執行resolve
操作。 -
Promise.reject()
方法:reject
方法類似于resolve
方法,只是會將Promise對象的狀態設置為rejected狀態,所以Promise.reject
無論傳過來的參數是什么狀態,都會直接作為reject
的參數傳遞到catch
的。 -
Promise.all()
方法:Promise.all()
我們在上一篇中講過,對于用法這里不再多做闡述,大致可以歸納為只有當所有的promise
都變成resolved
狀態時,原promise
才會變成resolved
狀態,相反當任意一個promise
變成rejected
狀態時,原promise
就會變成rejected
狀態,并且仍然處于pending
狀態的promise
將不會獲取到結果。不明白的同學請自行查閱Promise 與async/await。 -
Promise.allSettled()
方法:Promise.allSettled
是ES11(ES2020)中新添加的API,它用于解決Promise.all()
方法的一個缺陷(也是其特征):當有一個Promise
變成rejected
狀態時,新Promise
就會立即變成對應的rejected
狀態。 Promise.allSettled方法會在所有的
Promise都有結果時,無論是
resolved,還是
rejected,才會有最終的狀態,并且這個
Promise的結果一定是
resolved`。 -
Promise.race()
方法:Promise.race()
方法同樣在上一篇中講過,大致可以歸納為:數組中的其中一個promise
返回狀態時,無論此狀態是resolved
或者rejected
,它都將會成為原promise
的狀態,即先到先得。 -
Promise.any()
方法:Promise.any()
方法是ES12中新增的方法,和Promise.race()
方法是類似的。Promise.any()
方法會等到一個resolved
狀態,才會決定新Promise
的狀態,就算所有的Promise
都是rejected
的,那么也會等到所有的Promise
都變成rejected
狀態,err信息為:AggregateError: All promises were rejected
。
看完所有Promise
類方法的使用,接下來我們就開下怎么來實現他們吧。
const PROMISE_STATUS_PENDING = 'pending';
const PROMISE_STATUS_FULFILLED = 'fulfilled';
const PROMISE_STATUS_REJECTED = 'rejected';
// 工具函數
function execFunctionWithCatchError(exeFn, value, resolve, reject) {
try {
const result = exeFn(value);
resolve(result);
} catch (err) {
reject(err);
}
}
class myPromise {
// * 記錄狀態
constructor(executor) {
// * 保存Promise的狀態
this.status = PROMISE_STATUS_PENDING;
// * 保存傳入的值
this.value = undefined;
this.reason = undefined;
this.onResolvedFns = [];
this.onRejectedFns = [];
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;
// console.log('resolve被調用', this.value);
// * 執行then傳入進來的第一個回調函數
this.onResolvedFns.forEach(Fn => {
Fn(this.value);
});
});
}
};
const reject = reason => {
if (this.status == PROMISE_STATUS_PENDING) {
// * 添加微任務
queueMicrotask(() => {
if (this.status !== PROMISE_STATUS_PENDING) return;
this.status = PROMISE_STATUS_REJECTED;
this.reason = reason;
// console.log('reject被調用', this.reason);
// * 執行then傳入進來的第二個回調函數
this.onRejectedFns.forEach(Fn => {
Fn(this.reason);
});
});
}
};
// * 在調用executor時判斷里面是否拋出異常
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
// then方法
then(onResolved, onRejected) {
onRejected =
onRejected ||
(err => {
throw err;
});
return new myPromise((resolve, reject) => {
// 如果then方法調用的時候,狀態已經確定下來了,應該直接執行的
if (this.status === PROMISE_STATUS_FULFILLED && onResolved) {
// onResolved(this.value);
execFunctionWithCatchError(onResolved, this.value, resolve, reject);
} else if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
// onRejected(this.reason);
execFunctionWithCatchError(
onRejected,
this.reason,
resolve,
reject,
);
} else {
// 將成功回調和失敗回調添加到數組中
if (onResolved)
this.onResolvedFns.push(() => {
execFunctionWithCatchError(
onResolved,
this.value,
resolve,
reject,
);
});
if (onRejected)
this.onRejectedFns.push(() => {
execFunctionWithCatchError(
onRejected,
this.reason,
resolve,
reject,
);
});
}
});
}
// * catch方法
catch(onRejected) {
this.then(undefined, onRejected);
}
// finally方法
finally(onFinally) {
return this.then(
() => {
onFinally();
},
() => {
onFinally();
},
);
}
// 類方法resolve
static resolve(value) {
return new myPromise((resolve, reject) => {
resolve(value);
});
}
// 類方法reject
static reject(reason) {
return new myPromise((resolve, reject) => {
reject(reason);
});
}
// 類方法all
static all(promises) {
// * 問題關鍵:什么時候執行resolve,什么時候執行reject
return new myPromise((resolve, reject) => {
let values = [];
promises.forEach(promise => {
promise
.then(res => {
values.push(res);
if (values.length == promises.length) resolve(values);
})
.catch(err => {
reject(err);
});
});
});
}
// 類方法allSettled
static allSettled(promises) {
return new myPromise((resolve, reject) => {
let results = [];
promises.forEach(promise => {
promise
.then(res => {
results.push({ status: PROMISE_STATUS_FULFILLED, value: res });
if (results.length == promises.length) resolve(results);
})
.catch(err => {
results.push({ status: PROMISE_STATUS_REJECTED, value: err });
if (results.length == promises.length) resolve(results);
});
});
});
}
// 類方法race
static race(promises) {
return new myPromise((resolve, reject) => {
promises.forEach(promise => {
// promise.then(res=>{
// resolve(res);
// }).catch(err=>{
// reject(err);
// })
promise.then(resolve, reject);
});
});
}
// 類方法any
static any(promises) {
// * resolve 必須等待有一個成功的結果
// * reject 所有的都失敗才執行 reject
return new myPromise((resolve, reject) => {
let reasons = [];
promises.forEach(promise => {
promise
.then(res => {
resolve(res);
})
.catch(err => {
reasons.push(err);
if (reasons.length == promises.length) {
reject(
new AggregateError(
reasons,
' AggregateError: All promises were rejected',
),
);
}
});
});
});
}
}
說了這么多,最后放上一張Promise
的知識點關系圖作為結束。
promise.png