自己动手写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;
};
})();
复制代码