自己動手寫Promise
相信作為一名JSer,大家如今肯定都對Promise的使用非常熟悉了。Promise的出現,大大改善了js程式碼『回撥地獄』的問題,再結合async/await等語法特性,可以讓JS書寫簡潔優美、可讀性高的非同步程式碼。
在Promise規範化的路上,社群的貢獻可謂至關重要。早期各種版本的Promise庫最終推動了Promises/A+規範的達成,並最終被納入語言規範。如今隨著async/await的出現,使得JS中的非同步程式設計變得更加優雅、簡潔。
今天我準備和大家一起,嘗試自己動手實現一個簡略版本的Promise的polyfill。
自己動手寫Promise
Promise建構函式與狀態
我們的Promise將處於以下三種狀態中的一種:
- PENDING:待定狀態,也是Promise的初始狀態
- FULFILLED:已滿足,即該Promise已被resolve
- REJECTED:已拒絕,即該Promise已被reject
PENDING狀態可以轉換為FULFILLED或REJECTED狀態,而後兩者不能再次轉換。
const STATUS = {
PENDING: Symbol('PENDING'),
FULFILLED: Symbol('FULFILLED'),
REJECTED: Symbol('REJECTED'),
}
複製程式碼
在Promise建構函式中,我們會初始化一些屬性:將狀態置為PENDING,初始化回撥陣列。
我們將接收一個函式作為引數executor
,隨後這個函式將立即被呼叫,同時傳入兩個函式作為引數,呼叫這兩個函式將分別resolve和reject當前promise:
class Promise {
constructor(executor) {
this.status = STATUS.PENDING;
this.handlers = [];
this._resolveFromExecutor(executor);
}
}
複製程式碼
執行executor
接著我們執行executor,要注意的是執行時我們要使用try/catch,如果發生異常,則使用丟擲的異常reject當前promise。executor也可以主動resolve或reject當前promise:
_resolveFromExecutor(executor) {
const r = this._execute(executor, (value) => {
this._resolveCallback(value);
}, (reason) => {
this._rejectCallback(reason);
});
if (r !== undefined) {
this._rejectCallback(r);
}
}
複製程式碼
resolve與reject
_resolveCallback與_rejectCallback都需要率先判斷當前promise的狀態是否為PENDING:若狀態非PENDING則直接忽視呼叫;否則設定狀態為FULFILLED或REJECTED,並且將值或拒絕原因記錄下來,同時非同步處理回撥:
_resolveCallback(value) {
if (this.status !== STATUS.PENDING) return;
return this._fulfill(value);
}
_fulfill(value) {
if (this.status !== STATUS.PENDING) return;
this.status = STATUS.FULFILLED;
this._value = value;
async.settlePromises(this);
}
_rejectCallback(reason) {
this._reject(reason);
}
_reject(reason) {
if (this.status !== STATUS.PENDING) return;
this.status = STATUS.REJECTED;
this._reason = reason;
async.settlePromises(this);
}
複製程式碼
非同步處理
這裡的async.settlePromises會非同步呼叫promise._settlePromises。
js有許多非同步執行的方式,包括簡單的setTimeout、requestAnimationFrame,node環境下的nextTick、setImmediate,還有一些方法比如利用圖片載入error或是MutationObserver等等。這裡偷個懶直接用setTimeout了。
const async = {
schedule(fn) {
setTimeout(fn, 0);
},
settlePromises(promise) {
this.schedule(() => {
promise._settlePromises();
});
},
};
複製程式碼
_settlePromises將逐個執行handlers陣列中註冊的回撥,並在此後清空handlers陣列。在此實現_settlePromises方法之前,先來看看是如何向handlers陣列新增回撥的。
then與catch
then與cacth將呼叫_addCallbacks向handlers陣列新增回撥:
_addCallbacks(fulfill, reject, promise) {
this.handlers.push({
fulfill,
reject,
promise,
});
}
複製程式碼
而then與catch是對私有方法_then的進一步包裝:
then(didFulfill, didReject) {
return this._then(didFulfill, didReject);
}
catch(fn) {
return this.then(undefined, fn);
}
複製程式碼
每當呼叫_then方法將生成一個新的promise例項並返回:
_then(didFulfill, didReject) {
const promise = new Promise(INTERNAL);
let handler;
let value;
this._addCallbacks(didFulfill, didReject, promise);
return promise;
}
複製程式碼
這裡我們傳入的executor將不會呼叫resolve或reject改變promise狀態,而是將其加入父級promise的handlers陣列並在父級_settlePromises時處理,由此形成了promise鏈:
parentPromise.then -> 生成childPromise並返回 -> 加入parentPromise的handlers
parentPromise._settlePromises -> 執行childPromise的_fulfill或_reject
複製程式碼
_settlePromises
_settlePromises會遍歷handlers並呼叫_settlePromise。如果_then加入了回撥函式,那我們需要呼叫這個函式並根據其結果去resolve或reject目標promise;否則直接用原本的結果來resolve或reject目標promise:
_settlePromises() {
this.handlers.forEach(({ fulfill, reject, promise }) => {
if (this.status === STATUS.FULFILLED) {
this._settlePromise(promise, fulfill, this._value);
} else {
this._settlePromise(promise, reject, this._reason);
}
});
this.handlers.length = 0;
}
_settlePromise(promise, handler, value) {
if (typeof handler === 'function') {
this._settlePromiseFromHandler(handler, value, promise);
} else {
if (promise.status === STATUS.FULFILLED) {
promise._fulfill(value);
} else {
promise._reject(value);
}
}
}
_settlePromiseFromHandler(handler, value, promise) {
const x = tryCatch(handler).call(null, value);
if (x === errorObj) {
promise._reject(x.e);
} else {
promise._resolveCallback(x);
}
}
複製程式碼
Promise.resolve與Promise.reject
接著新增兩個靜態方法,返回一個promise示例,並立刻用傳入的值resolve或reject這個promise。
Promise.resolve = function resolve(v) {
return new Promise((res) => {
res(v);
});
};
Promise.reject = function reject(v) {
return new Promise((_, rej) => {
rej(v);
});
};
複製程式碼
立即執行回撥
當然,以上的程式碼並不會正確執行。
首先我們來看一下_then方法。我們需要判斷當前promise是否是PENDING狀態:如果是則將回撥加入handlers陣列;否則立即執行回撥:
const async = {
...
invoke(fn, receiver, arg) {
this.schedule(() => {
fn.call(receiver, arg);
});
},
};
_then(didFulfill, didReject) {
const promise = new Promise(INTERNAL);
const target = this;
let handler;
let value;
if (target.status !== STATUS.PENDING) {
if (target.status === STATUS.FULFILLED) {
handler = didFulfill;
value = target._value;
} else if (target.status === STATUS.REJECTED) {
handler = didReject;
value = target._reason;
}
async.invoke(
function ({ promise, handler, value }) {
this._settlePromise(promise, handler, value);
},
target,
{
handler,
promise,
value,
}
);
} else {
target._addCallbacks(didFulfill, didReject, promise);
}
return promise;
}
複製程式碼
當resolve的值為promise例項
接下來還有一個問題要處理,如果一個promise被另一個promise所resolve,則需要進行特別的處理。
如果作為值的promise已經非PENDING狀態,那比較簡單,直接用它的結果resolve或reject當前的promise即可。如果目標promise還在PENDING狀態,則將當前的promise以及它的handlers轉交給目標promise。因為當前的promise可能也被作為其他promise的resolve的值,因此這裡也要維護一個上級狀態,以便找到鏈的最前端:
_resolveCallback(value) {
if (this.status !== STATUS.PENDING) return;
if (!(value instanceof Promise)) return this._fulfill(value);
const p = value._target();
if (p.status === STATUS.PENDING) {
const len = this.handlers.length;
this.handlers.forEach(({ fulfill, reject, promise }) => {
p._addCallbacks(fulfill, reject, promise);
});
this._isFollowing = true;
this.handlers.length = 0;
this._followee = p;
} else if (p.status === STATUS.FULFILLED) {
this._fulfill(p._value);
} else if (p.status === STATUS.REJECTED) {
this._reject(p._reason);
}
}
_target() {
let ret = this;
while (ret._isFollowing) ret = ret._followee;
return ret;
}
複製程式碼
同時當我們呼叫promise._then時,也需要使用這個追溯機制:
_then(didFulfill, didReject) {
const promise = new Promise(INTERNAL);
const target = this;
...
}
複製程式碼
Promise.all
最後我們實現一下Promise.all。這裡的思路很簡單,生成一個promise示例,對傳入的陣列中的所有promise用then監聽結果,如果全部resolve則用所有結果組成的陣列resolve返回的promise,有一個失敗則立即用這個錯誤reject:
class PromiseArray {
constructor(values, count, isAll) {
this._ps = values;
this._count = isAll ? values.length : count;
this._isAll = isAll;
this._values = [];
this._valueCount = 0;
this._reasons = [];
this._reasonCount = 0;
this._promise = new Promise(INTERNAL);
this._iterate();
}
_iterate() {
let p;
for (let i = 0; i < this._ps.length; i++) {
p = this._ps[i];
p.then(function (index, value) {
if (this._isAll) {
this._values[index] = value;
} else {
this._values.push(value);
}
this._valueCount++;
this._check();
}.bind(this, i), function (index, reason) {
if (this._isAll) {
this._reasons[index] = reason;
} else {
this._reasons.push(reason);
}
this._reasonCount++;
this._check();
}.bind(this, i));
}
}
_check() {
if (this._count <= this._valueCount) {
this._promise._fulfill(this._values);
} else if (this._ps.length - this._count < this._reasonCount) {
this._promise._reject(this._reasons);
}
}
}
Promise.all = function (values) {
return new PromiseArray(values, undefined, true)._promise;
};
複製程式碼
小結
實現Promise的關鍵點在於如何實現Promise鏈。
使用Promise以及async/await將大大提高程式碼的可讀性、降低複雜度。
完整程式碼
(function () {
const errorObj = {};
let tryCatchTarget;
const tryCatcher = function tryCatcher() {
try {
const target = tryCatchTarget;
tryCatchTarget = null;
return target.apply(this, arguments);
} catch (e) {
errorObj.e = e;
return errorObj;
}
};
const tryCatch = function tryCatch(fn) {
tryCatchTarget = fn;
return tryCatcher;
};
const async = {
schedule(fn) {
setTimeout(fn, 0);
},
invoke(fn, receiver, arg) {
this.schedule(() => {
fn.call(receiver, arg);
});
},
settlePromises(promise) {
this.schedule(() => {
promise._settlePromises();
});
},
};
const INTERNAL = function INTERNAL() {};
const STATUS = {
PENDING: Symbol('PENDING'),
FULFILLED: Symbol('FULFILLED'),
REJECTED: Symbol('REJECTED'),
}
class Promise {
constructor(executor) {
this.status = STATUS.PENDING;
this.handlers = [];
this._isFollowing = false;
this._followee = null;
this._resolveFromExecutor(executor);
}
_resolveFromExecutor(executor) {
// if (executor === INTERNAL) return;
const r = this._execute(executor, (value) => {
this._resolveCallback(value);
}, (reason) => {
this._rejectCallback(reason);
});
if (r !== undefined) {
this._rejectCallback(r);
}
}
_execute(executor, resolve, reject) {
try {
executor(resolve, reject);
} catch (e) {
return e;
}
}
_resolveCallback(value) {
if (this.status !== STATUS.PENDING) return;
if (!(value instanceof Promise)) return this._fulfill(value);
const p = value._target();
if (p.status === STATUS.PENDING) {
const len = this.handlers.length;
this.handlers.forEach(({ fulfill, reject, promise }) => {
p._addCallbacks(fulfill, reject, promise);
});
this._isFollowing = true;
this.handlers.length = 0;
this._followee = p;
} else if (p.status === STATUS.FULFILLED) {
this._fulfill(p._value);
} else if (p.status === STATUS.REJECTED) {
this._reject(p._reason);
}
}
_target() {
let ret = this;
while (ret._isFollowing) ret = ret._followee;
return ret;
}
_fulfill(value) {
if (this.status !== STATUS.PENDING) return;
this.status = STATUS.FULFILLED;
this._value = value;
async.settlePromises(this);
}
_rejectCallback(reason) {
this._reject(reason);
}
_reject(reason) {
if (this.status !== STATUS.PENDING) return;
this.status = STATUS.REJECTED;
this._reason = reason;
async.settlePromises(this);
}
then(didFulfill, didReject) {
return this._then(didFulfill, didReject);
}
_then(didFulfill, didReject) {
const promise = new Promise(INTERNAL);
const target = this._target();
let handler;
let value;
if (target.status !== STATUS.PENDING) {
if (target.status === STATUS.FULFILLED) {
handler = didFulfill;
value = target._value;
} else if (target.status === STATUS.REJECTED) {
handler = didReject;
value = target._reason;
}
async.invoke(
function ({ promise, handler, value }) {
this._settlePromise(promise, handler, value);
},
target,
{
handler,
promise,
value,
}
);
} else {
target._addCallbacks(didFulfill, didReject, promise);
}
return promise;
}
catch(fn) {
return this.then(undefined, fn);
}
_addCallbacks(fulfill, reject, promise) {
this.handlers.push({
fulfill,
reject,
promise,
});
}
_settlePromises() {
this.handlers.forEach(({ fulfill, reject, promise }) => {
if (this.status === STATUS.FULFILLED) {
this._settlePromise(promise, fulfill, this._value);
} else {
this._settlePromise(promise, reject, this._reason);
}
});
this.handlers.length = 0;
}
_settlePromise(promise, handler, value) {
if (typeof handler === 'function') {
this._settlePromiseFromHandler(handler, value, promise);
} else {
if (promise.status === STATUS.FULFILLED) {
promise._fulfill(value);
} else {
promise._reject(value);
}
}
}
_settlePromiseFromHandler(handler, value, promise) {
const x = tryCatch(handler).call(null, value);
if (x === errorObj) {
promise._reject(x.e);
} else {
promise._resolveCallback(x);
}
}
}
Promise.resolve = function resolve(v) {
return new Promise((res) => {
res(v);
});
};
Promise.reject = function reject(v) {
return new Promise((_, rej) => {
rej(v);
});
};
window.Promise = Promise;
class PromiseArray {
constructor(values, count, isAll) {
this._ps = values;
this._count = isAll ? values.length : count;
this._isAll = isAll;
this._values = [];
this._valueCount = 0;
this._reasons = [];
this._reasonCount = 0;
this._promise = new Promise(INTERNAL);
this._iterate();
}
_iterate() {
let p;
for (let i = 0; i < this._ps.length; i++) {
p = this._ps[i];
p.then(function (index, value) {
if (this._isAll) {
this._values[index] = value;
} else {
this._values.push(value);
}
this._valueCount++;
this._check();
}.bind(this, i), function (index, reason) {
if (this._isAll) {
this._reasons[index] = reason;
} else {
this._reasons.push(reason);
}
this._reasonCount++;
this._check();
}.bind(this, i));
}
}
_check() {
if (this._count <= this._valueCount) {
this._promise._fulfill(this._values);
} else if (this._ps.length - this._count < this._reasonCount) {
this._promise._reject(this._reasons);
}
}
}
Promise.all = function (values) {
return new PromiseArray(values, undefined, true)._promise;
};
Promise.some = function (values, count) {
return new PromiseArray(values, count, false)._promise;
};
Promise.any = function (values) {
return new PromiseArray(values, 1, false)._promise;
};
})();
複製程式碼