Promise是web前端工程師在面試的過程中很難繞過的一個坎。如果您目前處於對Promise一知半解,或僅僅是停留在可以使用的層面上,建議您跟着本文敲打練習一遍,相信您一定會有所收穫!另外文章有點長……
一、實現Promise
以下功能:
const p1 = new Promise((resolve, reject) => {
resolve("成功");
})
p1.then(value => {
// 成功
console.log(value);
})
const p2 = new Promise((resolve, reject) => {
reject("失敗");
})
p2.then(undefined, reason => {
// 失敗
console.log(reason);
})
const p3 = new Promise((resolve, reject) => {
throw "異常"
})
p3.then(undefined, reason => {
// 異常
console.log(reason);
})
1、根據上面的代碼,我們不難推斷出其基本的結構:
/*
* 創建一個構造函數 Promise
* 該函數接收一個 executor 執行函數
* */
function Promise(executor) {
}
/*
* 爲 Promise 函數增加 then 方法;
* then 方法接收兩個類型爲 function 的參數;
* 第一個參數onResolved爲成功時調用的函數;
* 第二個參數onRejected爲失敗時調用的函數;
* */
Promise.prototype.then = function (onResolved,onRejected) {
}
2、Promise
對象存在三種狀態:Pending(進行中)
、resolved(已成功)
、rejected(已失敗)
。我們可以將其設置爲三個常量:
// 進行中狀態
const _PENDING="pending";
// 已成功狀態
const _RESOLVED="resolved";
// 已失敗狀態
const _REJECTED="rejected";
3、我們在實例化Promise
時,resolve
或reject
函數將會得到執行,一旦執行其實例的狀態會由pending
更改爲resolved
或rejected
,然後在原型對象方法then
當中進行接收。所以我們要幹以下幾個事情:
- 實例中創建兩個屬性
status
與value
- 創建內部函數
_resolve
與_reject
用於更新status
與value
- 立即執行
executor
函數
代碼如下:
function Promise(executor){
/****** 1、實例中創建兩個屬性`status`與`value` ******/
// 設置狀態初始值爲 pending
this.status = _PENDING;
// 設置初始值爲 undefined
this.value = undefined;
/****** 2、創建內部函數`_resolve`與`_reject`用於更新`status`與`value` ******/
// 成功時執行
function _resolve(value) {
// 修改 promise 對象的狀態爲 resolve
this.status = _RESOLVED;
// 保存成功的數據
this.value = value;
}
// 失敗時執行
function _reject(reason) {
// 修改 promise 對象的狀態爲 resolve
this.status = _REJECTED;
// 保存失敗的數據
this.value = reason;
}
/****** 3、立即執行`executor`函數 ******/
try{
// 立即執行 executor
executor(_resolve.bind(this),_reject.bind(this))
}catch (err) {
_reject.call(this,err);
}
}
4、我們需要在then
函數內,根據實例的狀態state
來判斷要執行onResolved
成功函數,還是onRejected
失敗函數。Promise
的核心then
函數初始代碼如下:
Promise.prototype.then = function (onResolved,onRejected) {
switch (this.status) {
// 當狀態爲resolve時,執行onResolved,並傳遞結果
case _RESOLVED:
onResolved(this.value);
break
// 當狀態爲reject時,執行onRejected,並傳遞結果
case _REJECTED:
onRejected(this.value);
break
}
}
5、當前已完成代碼如下:
// 進行中狀態
const _PENDING="pending";
// 已成功狀態
const _RESOLVED="resolved";
// 已失敗狀態
const _REJECTED="rejected";
/*
* 創建一個構造函數 Promise
* 該函數接收一個 executor 執行函數
* */
function Promise(executor){
// 設置狀態初始值爲 pending
this.status = _PENDING;
// 設置初始值爲 undefined
this.value = undefined;
// 成功時執行
function _resolve(value) {
// 修改 promise 對象的狀態爲 resolve
this.status = _RESOLVED;
// 保存成功的數據
this.value = value;
}
// 失敗時執行
function _reject(reason) {
// 修改 promise 對象的狀態爲 resolve
this.status = _REJECTED;
// 保存失敗的數據
this.value = reason;
}
try{
// 立即執行 executor
executor(_resolve.bind(this),_reject.bind(this))
}catch (err) {
_reject.call(this,err);
}
}
/*
* 爲 Promise 函數增加 then 方法;
* then 方法接收兩個類型爲 function 的參數;
* 第一個參數onResolved爲成功時調用的函數;
* 第二個參數onRejected爲失敗時調用的函數;
* */
Promise.prototype.then = function (onResolved,onRejected) {
switch (this.status) {
// 當狀態爲resolve時,執行onResolved,並傳遞結果
case _RESOLVED:
onResolved(this.value);
break
// 當狀態爲reject時,執行onRejected,並傳遞結果
case _REJECTED:
onRejected(this.value);
break
}
}
二、Promise的狀態state只允許更改一次
Promise
即承諾,一旦承諾便會給予結果,且結果是不允許更改的。也就是說狀態state
一旦確定便不可更改。
const p1 = new Promise((resolve, reject) => {
resolve("成功");
reject("失敗");
})
p1.then(value => {
// 不會執行
console.log(value);
},reason=>{
// 輸出:失敗
console.log(reason);
})
以上代碼正確的輸出應該是成功
,而我們自己封裝的Promise
輸出結果卻爲失敗
!說明我們的state
被reject("失敗")
進行了二次更改,原因很簡單:我們沒有對當前的狀態進行判斷。
所以我們要對_resolve
與_reject
進行調整,分別在其函數體內增加對當前狀態的判斷,如果不是初始狀態pending
則不會繼續更新狀態及數據。
在構造函數Promise
中找到_resolve
與_reject
函數,代碼調整如下:
// 成功時執行
function _resolve(value) {
// 如果爲pending退出函數
if (this.status !== "pending")
return;
// 修改 promise 對象的狀態爲 resolve
this.status = _RESOLVED;
// 保存成功的數據
this.value = value;
}
// 失敗時執行
function _reject(reason) {
// 如果爲pending退出函數
if (this.status !== "pending")
return;
// 修改 promise 對象的狀態爲 resolve
this.status = _REJECTED;
// 保存失敗的數據
this.value = reason;
}
接下來,再來執行最初的程序,輸出爲成功
,說明狀態只允許被更新一次了!
三、then函數是異步的
因爲then函數是異步的,所以在正常情況下,以下代碼的輸出應該爲:1 2 3
。但是採用我們自己封裝的Promise
,其結果卻爲1 3 2
。原因:我們並未對then
函數進行異步的處理。
const p = new Promise((resolve, reject) => {
console.log(1);
resolve("成功");
})
p.then(value=>{
console.log(3);
console.log()
})
console.log(2);
接下來,進入到then
函數中。我們只需要將執行回調的代碼用setTimeout
進行包裹即可:
Promise.prototype.then = function (onResolved, onRejected) {
switch (this.status) {
// 當狀態爲resolve時,執行onResolved,並傳遞結果
case _RESOLVED:
// 通過 setTimeout 讓代碼異步執行
setTimeout(() => {
onResolved(this.value);
})
break
// 當狀態爲reject時,執行onRejected,並傳遞結果
case _REJECTED:
// 通過 setTimeout 讓代碼異步執行
setTimeout(() => {
onRejected(this.value);
})
break
}
}
再來執行,結果爲1 2 3
。問題順利解決!
四、完成Promise的連綴調用(核心)
const p = new Promise((resolve, reject) => {
resolve("成功");
})
const result = p.then();
// 輸出status爲 resolved 的 Promise 實例
console.log(result);
const p2 = new Promise((resolve, reject) => {
resolve("失敗");
})
const result2 = p2.then();
// 輸出status爲 resolved 的 Promise 實例
console.log(result2);
const p3 = new Promise((resolve, reject) => {
throw "異常"
})
const result3 = p3.then();
// 輸出status爲 rejected 的 Promise 實例
console.log(result3);
1、正常情況下,上方代碼不管成功與失敗,then
函數的返回結果始終應該是一個Promise
實例,且其狀態均爲resolved
。如果出現異常報錯,則返回的狀態爲rejected
,如下:
但是,我們目前的then
函數是沒有返回值的,所以我們只能得到一個undefined
,並且由於我們未給予then函數相對應的參數(類型爲函數),還給我們飄紅報錯了:Uncaught TypeError: onResolved is not a function
。
所以接下來,我們要做三件事:1、驗證參數是否爲函數。 2、讓then
函數直接返回Promise
3、更改promise
的狀態:異常執行reject
,其它均執行resolve
- 驗證參數是否爲函數:
// 防止使用者不傳成功或失敗回調函數,所以成功失敗回調都給了默認回調函數
onResolved = typeof onResolved === "function" ? onResolved : value => value;
onRejected = typeof onRejected === "function" ? onRejected : error => { throw error };
- 讓
then
函數返回promise
,then
函數完整代碼如下:
Promise.prototype.then = function (onResolved, onRejected) {
return new Promise((resolve, reject) => {
// 防止使用者不傳成功或失敗回調函數,所以成功失敗回調都給了默認回調函數
onResolved = typeof onResolved === "function" ? onResolved : value => value;
onRejected = typeof onRejected === "function" ? onRejected : error => {
throw error
};
switch (this.status) {
// 當狀態爲resolve時,執行onResolved,並傳遞結果
case _RESOLVED:
// 通過 setTimeout 讓代碼異步執行
setTimeout(() => {
onResolved(this.value);
});
break
// 當狀態爲reject時,執行onRejected,並傳遞結果
case _REJECTED:
// 通過 setTimeout 讓代碼異步執行
setTimeout(() => {
onRejected(this.value);
});
break
}
})
}
- 更改
Promise
的狀態:異常執行reject
,其它均執行resolve
,並傳值。
Promise.prototype.then = function (onResolved, onRejected) {
return new Promise((resolve, reject) => {
// 防止使用者不傳成功或失敗回調函數,所以成功失敗回調都給了默認回調函數
onResolved = typeof onResolved === "function" ? onResolved : value => value;
onRejected = typeof onRejected === "function" ? onRejected : error => {
throw error
};
switch (this.status) {
// 當狀態爲resolve時,執行onResolved,並傳遞結果
case _RESOLVED:
// 通過 setTimeout 讓代碼異步執行
setTimeout(() => {
// 增加try方法,如果出現異常,執行reject
try {
onResolved(this.value);
// 無異常執行resolve
resolve(this.value);
} catch (err) {
// 出現異常,執行reject
reject(err);
}
});
break
// 當狀態爲reject時,執行onRejected,並傳遞結果
case _REJECTED:
// 通過 setTimeout 讓代碼異步執行
setTimeout(() => {
// 增加try方法,如果出現異常,執行reject
try {
onRejected(this.value);
// 無異常執行resolve
resolve(this.value);
} catch (err) {
// 出現異常,執行reject
reject(err);
}
});
break
}
})
}
接下來,通過咱們封裝的程序,可以得到準確的數據了:
2、我們知道then
在其回調函數中返回非Promise
的數據,最終得到的result
是一個爲resolved
狀態的Promise
(成功的狀態),倘若返回的是一個Promise
數據,那麼最終得到的便是該Promise
的狀態(成功或失敗的狀態),例如:
const p = new Promise((resolve, reject) => {
resolve("成功");
})
const result = p.then(value=>{
return "返回字符串"+value;
});
console.log("result",result);
const result2 = p.then(value=>{
return new Promise((resolve,reject)=>{
resolve("成功的Promise")
})
});
console.log("result2",result2);
const result3 = p.then(value=>{
return new Promise((resolve,reject)=>{
reject("失敗的Promise")
})
});
console.log("result3",result3);
應該是這樣的結果:
但是,通過我們自己封裝的
Promise
得到的結果都是一樣的:- 原因:沒有在
then
函數中判斷onResolved
與onRejected
返回類型。 - 解決:判斷
onResolved
與onRejected
的返回結果是否爲Promise,如果是Promise
,則將其狀態與then
要返回的Promise
狀態設爲一致。
Promise.prototype.then = function (onResolved, onRejected) {
return new Promise((resolve, reject) => {
// 防止使用者不傳成功或失敗回調函數,所以成功失敗回調都給了默認回調函數
onResolved = typeof onResolved === "function" ? onResolved : value => value;
onRejected = typeof onRejected === "function" ? onRejected : error => {
throw error
};
switch (this.status) {
// 當狀態爲resolve時,執行onResolved,並傳遞結果
case _RESOLVED:
// 通過 setTimeout 讓代碼異步執行
setTimeout(() => {
// 增加try方法,如果出現異常,執行reject
try {
let result = onResolved(this.value);
// 判斷返回結果是否爲Promise類型
if(result instanceof Promise){
// result.then(v => {
// // 成功,修改返回的 Promise 狀態爲成功
// resolve(v);
// }, r => {
// // 失敗,修改返回的 Promise 狀態爲失敗
// reject(r);
// });
// result 是 promise,下面這行代碼是上方代碼的簡寫形式
result.then(resolve,reject);
}else{
// 非Promise類型,將結果直接傳遞過去
resolve(result);
}
} catch (err) {
// 出現異常,執行reject
reject(err);
}
});
break
// 當狀態爲reject時,執行onRejected,並傳遞結果
case _REJECTED:
// 通過 setTimeout 讓代碼異步執行
setTimeout(() => {
// 增加try方法,如果出現異常,執行reject
try {
let result = onRejected(this.value);
// 判斷返回結果是否爲Promise類型
if(result instanceof Promise){
// result.then(v => {
// // 成功,修改返回的 Promise 狀態爲成功
// resolve(v);
// }, r => {
// // 失敗,修改返回的 Promise 狀態爲失敗
// reject(r);
// });
// result 是 promise,下面這行代碼是上方代碼的簡寫形式
result.then(resolve,reject);
}else{
// 非Promise類型,將結果直接傳遞過去
resolve(result);
}
} catch (err) {
// 出現異常,執行reject
reject(err);
}
});
break
}
})
}
結果:
效果雖然出來了,但是這樣的代碼確實有些臃腫。所以我們要對其進行優化:不難發現兩個
case
內的代碼除了回調函數不同,其它都是一樣的,所以我們可以將其進行封裝,並將回調函數作爲參數傳遞。封裝優化後:
Promise.prototype.then = function (onResolved, onRejected) {
return new Promise((resolve, reject) => {
/*
* 參數 cb 的值爲 onResolved 或 onRejected 函數
* */
function _callback(cb) {
// 增加try方法,如果出現異常,執行reject
try {
let result = cb(this.value);
// 判斷返回結果是否爲Promise類型
if (result instanceof Promise) {
// result 是 promise,下面這行代碼是上方代碼的簡寫形式
result.then(resolve, reject);
} else {
// 非Promise類型,將結果直接傳遞過去
resolve(result);
}
} catch (err) {
// 出現異常,執行reject
reject(err);
}
}
// 防止使用者不傳成功或失敗回調函數,所以成功失敗回調都給了默認回調函數
onResolved = typeof onResolved === "function" ? onResolved : value => value;
onRejected = typeof onRejected === "function" ? onRejected : error => {
throw error
};
switch (this.status) {
// 當狀態爲resolve時,執行onResolved,並傳遞結果
case _RESOLVED:
// 通過 setTimeout 讓代碼異步執行
setTimeout(() => {
_callback.call(this, onResolved);
});
break;
// 當狀態爲reject時,執行onRejected,並傳遞結果
case _REJECTED:
// 通過 setTimeout 讓代碼異步執行
setTimeout(() => {
_callback.call(this, onRejected);
});
break;
}
})
}
3、程序寫到這一步其實還隱藏着一個bug,如果我們採用連綴的形式會有問題,代碼如下:
let p1 = new Promise((resolve, reject) => {
resolve('成功')
}).then(value => {
console.log("我會輸出")
}).then(value => {
console.log("我不會輸出")
})
正常來講,兩個then
均會輸出纔對。而通過我們封裝的Promise
,只會將第一個輸出。
- 分析:因爲我們現在的Promise是同步任務,所以當我們執行到第一個
then
的時候,當前Promise
的狀態已經確定爲resolved。而當執行到第二個then
的時候,此時的Promise
是通過第一個then
得到的,又因爲在第一個then
當中有setTimeout
,使其變爲了異步,所以會造成resolve
或reject
不會立即調用,最終導致在執行第二個then時,當前Promise
的status
爲pending
。也就是說我們更改狀態後,回調方法沒有得到執行。如果此時我們將封裝then
函數當中的setTimeout
移除掉,則會恢復正常,但將其移除掉封裝也就失去了意義。 - 解決:我們已經知道原因是當
Promise
的狀態發生變化時,then
函數的回調沒有得到調用。所以我們需要在改變狀態後調用即可。可狀態更改完成之後我們又如何纔可以執行回調?在這個時候我們可以在實例當中創建一個屬性onCallBacks
用於存放回調函數隊列,然後在執行then
函數時判斷當前狀態如果爲pending
則說明爲異步任務,只需將回調函數放置到onCallBacks
屬性中。這樣當異步修改完狀態後,我們就可以通過onCallBacks
執行回調了。代碼: - 在實例當中創建一個屬性
onCallBacks
用於存放回調函數隊列。
// 添加回調函數隊列
this.onCallBacks = [];
*在then
函數中判斷當前狀態爲pending
時,將回調函數放置到 onCallBacks
數組中。
// 當狀態爲 pending 時,將要執行的回調函數放置到隊列中,待狀態更改完畢後再調用。
case _PENDING:
this.onCallBacks.push({
onResolved() {
//獲取回調函數的執行結果
_callback.call(this,onResolved);
},
onRejected() {
_callback.call(this,onRejected);
}
});
break;
- 當異步修改完狀態後,我們就可以通過
onCallBacks
執行回調了。
// 成功時執行
function _resolve(value) {
if (this.status !== "pending")
return;
// 修改 promise 對象的狀態爲 resolve
this.status = _RESOLVED;
// 保存成功的數據
this.value = value;
//檢查回調數組中是否存在數據
if (this.onCallBacks.length > 0) {
// 異步執行
setTimeout(() => {
this.onCallBacks.forEach(onCb => {
onCb.onResolved.call(this);
});
});
}
}
// 失敗時執行
function _reject(reason) {
if (this.status !== "pending")
return;
// 修改 promise 對象的狀態爲 resolve
this.status = _REJECTED;
// 保存失敗的數據
this.value = reason;
//檢查回調數組中是否存在數據
if (this.onCallBacks.length > 0) {
// 異步執行
setTimeout(() => {
this.onCallBacks.forEach(onCb => {
onCb.onRejected.call(this);
});
});
}
}
Promise
完整代碼如下:
// 進行中狀態
const _PENDING = "pending";
// 已成功狀態
const _RESOLVED = "resolved";
// 已失敗狀態
const _REJECTED = "rejected";
/*
* 創建一個構造函數 Promise
* 該函數接收一個 executor 執行函數
* */
function Promise(executor) {
// 設置狀態初始值爲 pending
this.status = _PENDING;
// 設置初始值爲 undefined
this.value = undefined;
// 添加回調函數隊列
this.onCallBacks = [];
// 成功時執行
function _resolve(value) {
if (this.status !== "pending")
return;
// 修改 promise 對象的狀態爲 resolve
this.status = _RESOLVED;
// 保存成功的數據
this.value = value;
//檢查回調數組中是否存在數據
if (this.onCallBacks.length > 0) {
// 異步執行
setTimeout(() => {
this.onCallBacks.forEach(onCb => {
onCb.onResolved.call(this);
});
});
}
}
// 失敗時執行
function _reject(reason) {
if (this.status !== "pending")
return;
// 修改 promise 對象的狀態爲 resolve
this.status = _REJECTED;
// 保存失敗的數據
this.value = reason;
//檢查回調數組中是否存在數據
if (this.onCallBacks.length > 0) {
// 異步執行
setTimeout(() => {
this.onCallBacks.forEach(onCb => {
onCb.onRejected.call(this);
});
});
}
}
try {
// 立即執行 executor
executor(_resolve.bind(this), _reject.bind(this))
} catch (err) {
_reject.call(this, err);
}
}
/*
* 爲 Promise 函數增加 then 方法;
* then 方法接收兩個類型爲 function 的參數;
* 第一個參數 onResolved 爲成功時調用的函數;
* 第二個參數 onRejected 爲失敗時調用的函數;
* */
Promise.prototype.then = function (onResolved, onRejected) {
return new Promise((resolve, reject) => {
/*
* 參數 cb 的值爲 onResolved 或 onRejected 函數
* */
function _callback(cb) {
// 增加try方法,如果出現異常,執行reject
try {
let result = cb(this.value);
// 判斷返回結果是否爲Promise類型
if (result instanceof Promise) {
// result 是 promise,下面這行代碼是上方代碼的簡寫形式
result.then(resolve, reject);
} else {
// 非Promise類型,將結果直接傳遞過去
resolve(result);
}
} catch (err) {
// 出現異常,執行reject
reject(err);
}
}
// 防止使用者不傳成功或失敗回調函數,所以成功失敗回調都給了默認回調函數
onResolved = typeof onResolved === "function" ? onResolved : value => value;
onRejected = typeof onRejected === "function" ? onRejected : error => {
throw error
};
switch (this.status) {
// 當狀態爲resolve時,執行onResolved,並傳遞結果
case _RESOLVED:
// 通過 setTimeout 讓代碼異步執行
setTimeout(() => {
_callback.call(this, onResolved);
});
break;
// 當狀態爲reject時,執行onRejected,並傳遞結果
case _REJECTED:
// 通過 setTimeout 讓代碼異步執行
setTimeout(() => {
_callback.call(this, onRejected);
});
break;
// 當狀態爲 pending 時,將要執行的回調函數放置到隊列中,待狀態更改完畢後再調用。
case _PENDING:
this.onCallBacks.push({
onResolved() {
//獲取回調函數的執行結果
_callback.call(this,onResolved);
},
onRejected() {
_callback.call(this,onRejected);
}
});
break;
}
})
}
截止到目前,我們已經完成了Promise
的鏈式調用。
五、其它API
// catch方法的封裝
Promise.prototype.catch = function (onRejected) {
return this.then(undefined, onRejected);
}
// 函數對象 resolve 的封裝
Promise.resolve = function (value) {
return new Promise((resolve, reject) => {
if (value instanceof Promise) {
value.then(resolve, reject);
} else {
resolve(value);
}
});
}
// 函數對象 reject 的封裝
Promise.reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason);
})
}
//函數對象 all 的封裝
Promise.all = function (promises) {
return new Promise((resolve, reject) => {
let pValues = [];
let flag = 0;
for (let i = 0; i < promises.length; i++) {
promises[i].then(v => {
pValues[i] = v;
flag++;
if (flag >= promises.length) {
resolve(pValues);
}
}, r => {
reject(r);
})
}
});
}
// 函數對象 race
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
for(let i=0;i<promises.length;i++){
promises[i].then(value=>{
resolve(value);
}, reason=>{
reject(reason);
})
}
});
}
—————END—————
喜歡本文的朋友們,歡迎關注公衆號 張培躍,收看更多精彩內容!!