相信做爲一名JSer,你們現在確定都對Promise的使用很是熟悉了。Promise的出現,大大改善了js代碼『回調地獄』的問題,再結合async/await等語法特性,可讓JS書寫簡潔優美、可讀性高的異步代碼。前端
在Promise規範化的路上,社區的貢獻可謂相當重要。早期各類版本的Promise庫最終推進了Promises/A+規範的達成,並最終被歸入語言規範。現在隨着async/await的出現,使得JS中的異步編程變得更加優雅、簡潔。node
今天我準備和你們一塊兒,嘗試本身動手實現一個簡略版本的Promise的polyfill。git
咱們的Promise將處於如下三種狀態中的一種:github
PENDING狀態能夠轉換爲FULFILLED或REJECTED狀態,然後二者不能再次轉換。編程
const STATUS = {
PENDING: Symbol('PENDING'),
FULFILLED: Symbol('FULFILLED'),
REJECTED: Symbol('REJECTED'),
}
複製代碼
在Promise構造函數中,咱們會初始化一些屬性:將狀態置爲PENDING,初始化回調數組。數組
咱們將接收一個函數做爲參數executor
,隨後這個函數將當即被調用,同時傳入兩個函數做爲參數,調用這兩個函數將分別resolve和reject當前promise:promise
class Promise {
constructor(executor) {
this.status = STATUS.PENDING;
this.handlers = [];
this._resolveFromExecutor(executor);
}
}
複製代碼
接着咱們執行executor,要注意的是執行時咱們要使用try/catch,若是發生異常,則使用拋出的異常reject當前promise。executor也能夠主動resolve或reject當前promise:bash
_resolveFromExecutor(executor) {
const r = this._execute(executor, (value) => {
this._resolveCallback(value);
}, (reason) => {
this._rejectCallback(reason);
});
if (r !== undefined) {
this._rejectCallback(r);
}
}
複製代碼
_resolveCallback與_rejectCallback都須要率先判斷當前promise的狀態是否爲PENDING:若狀態非PENDING則直接忽視調用;不然設置狀態爲FULFILLED或REJECTED,而且將值或拒絕緣由記錄下來,同時異步處理回調:app
_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與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會遍歷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或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;
}
複製代碼
接下來還有一個問題要處理,若是一個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示例,對傳入的數組中的全部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;
};
})();
複製代碼