Promise 是異步編程的一種解決方案,比傳統的解決方案(回調函數和事件)更合理和更強大。如今前端應用中Promise已經獲得了普遍使用。本文經過實現符合Promise/A+規範的Promise,對其加深印象。javascript
咱們在使用Promise時,一般是使用new操做符進行構造,傳入resolver函數,該函數會接受成功(resolve)、失敗(reject)的回調函數,當咱們肯定結果時,須要調用resolve或reject,具體代碼以下:前端
const p1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('success'), 1000)
})
// 1s後控制檯打印success
p1.then(res => console.log(res))
複製代碼
因此咱們的Promise也須要是個構造函數,而且執行用戶傳入的resolver函數,將定義好的回調函數傳進去。下面是具體的代碼:java
注:本文代碼的實現,下劃線開頭表明私有屬性、私有方法。git
// 定義Promise的三種狀態常量
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
function Promise(resolver) {
// 傳入的必須是函數
if (typeof resolver !== 'function') {
throw new TypeError('Promise resolver ' + resolver + ' is not a function');
}
// resolve或reject的結果值
this._result = undefined;
// 狀態
this._status = PENDING;
try {
// 執行
resolver(this._resolve.bind(this), this._reject.bind(this));
} catch (error) {
// 捕獲錯誤
this._reject(error);
}
}
// 私有方法,傳給resolver的成功、失敗回調
Promise.prototype._resolve = function() {}
Promise.prototype._reject = function() {}
複製代碼
接下來咱們來實現_resolve、_reject私有方法,其實邏輯很簡單,咱們只須要改變Promise狀態,以及成功的值或者失敗的緣由。但要注意Promise一旦狀態改變,就不會再變,任什麼時候候均可以獲得這個結果,因此咱們只有在狀態時PENDING的時纔會執行。es6
Promise.prototype._resolve = function(value) {
// setTimeout 爲了異步執行
setTimeout(() => {
if (this._status !== PENDING) return;
this._status = FULFILLED;
this._result = value;
});
}
Promise.prototype._reject = function (reason) {
// setTimeout 爲了異步執行
setTimeout(() => {
if (this._status !== PENDING) return;
this._status = REJECTED;
this._result = reason;
});
};
複製代碼
Promise的核心就是then
方法,Promise/A+規範大都也是針對then方法進行闡述,實現了then
方法後,咱們再來實現其餘API就方便了不少。github
Promise的then
方法的規範有以下幾點編程
接受兩個參數,onFulfilled(成功回調), onRejected(失敗回調),當回調不是函數時, 其必須被忽略,支持透傳promise
then 方法能夠被同一個 Promise 調用屢次異步
then 方法必須返回一個 Promise 對象異步編程
咱們針對上述幾點分別來實現一下
關於第一點,能夠控制檯執行下面代碼
const p1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('success'), 1000)
})
// 1s後控制檯打印success
p1.then(1).then(res => console.log(res))
複製代碼
第二個then方法依然能夠接受到resolve成功的值,因此當then方法傳入的不是函數時,咱們要規範使其變成函數支持透傳。
Promise.prototype.then = function (onFulfilled, onRejected) {
// 保證是函數,不是函數要實現透傳
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => v;
onRejected = typeof onRejected === 'function' ? onRejected : (v) => { throw v };
}
複製代碼
實現第一點很簡單,咱們只須要吧把結果/錯誤 -> 返回/拋出傳遞給出去就能夠啦。
咱們再來看第二點,爲了能夠屢次調用而且依次執行,咱們須要改下以前寫過的代碼,咱們須要增長倆個回調隊列,成功、失敗各一個。其實也能夠用一個隊列來存儲,我這裏採用的分別存儲。
function Promise(resolver) {
// 忽略無關代碼...
// resolve的回調隊列
+ this._resolveCbs = [];
// reject的回調隊列
+ this._rejectCbs = [];
}
Promise.prototype._resolve = function(value) {
setTimeout(() => {
// 忽略無關代碼...
+ this._resolveCbs.forEach((callback) => callback(value));
});
}
Promise.prototype._reject = function(reason) {
setTimeout(() => {
// 忽略無關代碼...
+ this._rejectCbs.forEach((callback) => callback(reason));
});
}
複製代碼
咱們在then方法中,若是狀態還處於PENDING,就須要將傳入的onFulfilled(成功回調), onRejected(失敗回調)插入對應的隊列中,不然直接執行就好。這也就是咱們要實現第三點的核心邏輯。
根據第三點所述,咱們老是須要執行onFulfilled 或 onRejected,而後傳入Promise解決過程,此外還須要捕獲這個過程,直接reject。具體核心代碼以下
let promise = undefined;
return (promise = new Promise((resolve, reject) => {
setTimeout(() => {
try {
var x = onFulfilled(this._result); // 或者 var x = onRejected(this._result);
// resolvePromise Promise解決過程 下一段講
resolvePromise(promise, x, resolve, reject);
} catch (e) {
return reject(e);
}
});
}));
複製代碼
下面咱們就是把這段代碼分別用在 PENDING, FULFILLED, REJECTED三種狀態,完整代碼以下:
Promise.prototype.then = function (onFulfilled, onRejected) {
// 保證是函數,不是函數要實現透傳
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => v;
onRejected =
typeof onRejected === 'function'
? onRejected
: (v) => {
throw v;
};
let promise = undefined;
// 已經resolve
if (this._status === FULFILLED) {
return (promise = new Promise((resolve, reject) => {
setTimeout(() => {
try {
var x = onFulfilled(this._result);
resolvePromise(promise, x, resolve, reject);
} catch (e) {
return reject(e);
}
});
}));
}
// 已經reject
if (this._status === REJECTED) {
return (promise = new Promise((resolve, reject) => {
setTimeout(() => {
try {
var x = onRejected(this._result);
resolvePromise(promise, x, resolve, reject);
} catch (e) {
return reject(e);
}
});
}));
}
// pending時直接放入回調隊列中,放入隊列彙總不須要加setTimeout,由於執行時候已是setTimeout中
if (this._status === PENDING) {
return (promise = new Promise((resolve, reject) => {
this._resolveCbs.push((value) => {
try {
var x = onFulfilled(value);
resolvePromise(promise, x, resolve, reject);
} catch (e) {
return reject(e);
}
});
this._rejectCbs.push((reason) => {
try {
var x = onRejected(reason);
resolvePromise(promise, x, resolve, reject);
} catch (e) {
return reject(e);
}
});
}));
}
};
複製代碼
上面代碼看似不少,很複雜,但其實根據規範來看,其實很簡單,並且有大量的重複代碼。那咱們還有一個resolvePromise
函數沒有完成,接下來我但願讀者能夠本身去讀一下Promise 解決過程的邏輯。點擊連接去查看。由於函數的實現徹底照規範的邏輯書寫,沒有技巧可言。
這裏簡單的總結幾點:
爲了和其餘promise並存,咱們不能只判斷onFulfilled,onRejected函數返回的是不是promise,咱們只須要保證其返回值存在then
方法就去嘗試按promise處理。
若是沒有then屬性,或者then屬性不是函數的話,直接按照resolve(x)處理
若是then存在而且是函數,按照promise處理的同時,須要捕獲錯誤按reject(x)處理
若是傳入的回調均被調用,或者被同一參數調用了屢次,則優先採用首次調用並忽略剩下的調用(只能調用一次,須要有標誌位)
按照上述四點,咱們能夠寫出resolvePromise
函數的代碼
function resolvePromise(promise, x, resolve, reject) {
// 若是 promise 和 x 指向同一對象,以 TypeError 爲據因拒絕執行 promise
if (x === promise) {
return reject(new TypeError('Chaining cycle detected for promise!'));
}
// 用於 「優先採用首次調用並忽略剩下的調用」的標誌位
let invoked = false;
// 嘗試把 x.then 賦值給 then
let then = undefined;
// x 爲對象或函數
if ((x !== null && typeof x === 'object') || typeof x === 'function') {
try {
then = x.then;
if (typeof then === 'function') {
// 若是 then 是函數,將 x 做爲函數的做用域 this 調用之
then.call(
x,
(y) => {
if (invoked) return;
invoked = true;
return resolvePromise(promise, y, resolve, reject);
},
(r) => {
if (invoked) return;
invoked = true;
return reject(r);
}
);
} else {
// 若是 then 不是函數,以 x 爲參數執行 promise
return resolve(x);
}
} catch (e) {
// 若是取 x.then 的值時拋出錯誤 e ,則以 e 爲據因拒絕 promise
if (invoked) return;
invoked = true;
return reject(e);
}
} else {
// 若是 x 不爲對象或者函數,以 x 爲參數執行 promise
return resolve(x);
}
}
複製代碼
.catch()發生錯誤時的回調函數 至關於使用.then(null, onRejected),咱們實現了then方法,因此catch方法就至關簡單
Promise.prototype.catch = function (onRejected) {
return this.then(null, onRejected);
};
複製代碼
.finally()方法用於指定無論 Promise 對象最後狀態如何,都會執行的操做。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
複製代碼
上面代碼中,無論promise最後的狀態,在執行完then或catch指定的回調函數之後,都會執行finally方法指定的回調函數。
那咱們就能夠經過then方法,傳入的成功、失敗回調函數中都去執行callback
Promise.prototype.finally = function (callback) {
return this.then(
(value) => Promise.resolve(callback()).then(() => value),
(reason) =>
Promise.resolve(callback()).then(() => {
throw reason;
})
);
};
複製代碼
注意咱們要.then將結果透傳,由於finally後面還能夠繼續調用then方法。
// 最後一個then理應接受到2做爲參數
Promise.resolve(2).finally(() => { }).then(res => console.log(res))
複製代碼
有時須要將現有對象轉爲 Promise 對象,Promise.resolve()方法就起到這個做用。
咱們只須要把then方法中的成功邏輯拿出來使用就能夠。(其中x不是onFulfilled執行的值,直接是傳入的參數)
Promise.resolve = function (value) {
let promise;
return (promise = new Promise((resolve, reject) => {
resolvePromise(promise, value, resolve, reject);
}));
};
複製代碼
Promise.reject(reason)方法也會返回一個新的 Promise 實例,該實例的狀態爲rejected。
Promise.reject = function (reason) {
return new Promise((_, reject) => reject(reason));
};
複製代碼
/** * Promise.all()方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。 * 只有全部實例的狀態都變成fulfilled,新的實例狀態纔會變成fulfilled,新實例參數 * 只要其中有一個被rejected,新的實例就變成rejected,此時第一個被reject的實例的返回值,會傳遞給新實例的回調函數。 */
Promise.all = function (promises) {
return new Promise(function (resolve, reject) {
let resolvedCount = 0;
let promiseCount = promises.length;
let resolvedValues = new Array(promiseCount);
for (let i = 0; i < promiseCount; i++) {
Promise.resolve(promises[i]).then(
(value) => {
resolvedCount++;
resolvedValues[i] = value;
// 數量相同說明promise實例都是成功
if (resolvedCount == promiseCount) {
return resolve(resolvedValues);
}
},
(reason) => {
// 率先reject的直接失敗,傳入緣由
return reject(reason);
}
);
}
});
};
複製代碼
/** * Promise.race()方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。 * 只要其中有一個狀態變動,新的實例就跟隨着變動,參數會傳遞給新實例的回調函數。 */
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
for (var i = 0; i < promises.length; i++) {
// 誰快誰說了算!
Promise.resolve(promises[i]).then(
(value) => {
return resolve(value);
},
(reason) => {
return reject(reason);
}
);
}
});
};
複製代碼
Promise的實現難點主要集中在then方法上,其餘方法都是基於then方法實現的。實現一個Promise也是筆試的高頻題目,但願本文章能夠給你帶來幫助。