本文已参与「新人创作礼」活动,一起开启掘金创作之路。
我们自己来手写实现一个Promise类,我们既可以参考Promise/A+(promisesaplus.com/)的规范,也可以参照ES6(ES2015)中Promise的实现。
结构的设计
首先我们要明确的是Promise是一个类,并且它的构造函数需要传入一个回调函数executor作为参数,这个executor会在调用new Promise时被立即执行,并且在执行的时候会被传入两个函数(resolve和reject)作为参数,通过在executor中调用这两个函数来改变promise的状态;
promise的状态(status)在创建出来时是pending,在调用resovle时变成fulfilled,调用reject时变成rejected,并且状态一旦确定就不可改变了;
resolve在调用的时候接收一个value,作为promise的value;
reject在调用的时候接收一个reason,作为promise的reason;
于是我们实现了这样的一个MyPromise类:
const PROMISE_STATUS_PENDING = 'pending'
const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_REJECTED = 'rejected'
class MyPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING
this.value = undefined
this.reason = undefined
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
this.status = PROMISE_STATUS_FULFILLED
this.value = value
}
}
const reject = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
this.status = PROMISE_STATUS_REJECTED
this.reason = reason
}
}
executor(resolve, reject)
}
}
const promise = new MyPromise((resovle, reject) => {
console.log('executor会被立即执行');
resovle(1111)
reject(2222)
})
console.log(promise.status, promise.value, promise.reason); // fulfilled 1111 undefined
then方法设计
Promise存在一个then的实例方法,该方法会接收两个回调函数onFulfilled和onRejected作为参数,并且在Promise对象的状态确定之后调用;
onFulfilled会在Promise对象的状态变成fulfilled之后被调用,并传入Promise对象的value作为参数;
onRejected会在Promise对象的状态变成rejected之后被调用,并传入Promise对象的reason作为参数;
根据Promise/A+规范,onRejected和onFulfilled必须确保异步执行
const PROMISE_STATUS_PENDING = 'pending'
const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_REJECTED = 'rejected'
class MyPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING
this.value = undefined
this.reason = undefined
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
this.status = PROMISE_STATUS_FULFILLED
this.value = value
queueMicrotask(() => {
this.onFulfilled(value)
})
}
}
const reject = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
this.status = PROMISE_STATUS_REJECTED
this.reason = reason
queueMicrotask(() => {
this.onRejected(reason)
})
}
}
executor(resolve, reject)
}
then(onFulfilled, onRejected) {
this.onFulfilled = onFulfilled
this.onRejected = onRejected
}
}
const promise = new MyPromise((resolve, reject) => {
resolve(111)
reject(222)
})
// onFulfilled和onRejected异步执行:实现方式可以通过宏任务或微任务
promise.then((value) => {
console.log('value:', value);
}, (reason) => {
console.log('reason:', reason);
})
then方法的优化一
上面我们虽然借助微任务实现了then方法,但还有一些情况我们没考虑进去:
- then方法需要能够被多次调用;
按照我们上面的实现,then方法被调用多次时,之后最后的那个then方法传入的回调函数会被执行;
- 如果在then方法调用的时候,promise的状态已经确定了;
在我们的代码实现中,通过将promise对象的状态改变和对应回调函数的执行加入到微任务队列中,使得我们能够在promise对象状态改变的时候,执行then方法中传入的回调函数,当我们的then方法调用都放到同步代码中时,这样做是没问题的;
但是,如果我们在setTimeout中传入的回调函数中执行then方法,此时代码的运行结果就会出乎意料了;由于setTimeout传入的回调函数会被加入到宏任务中执行,而对于then方法传入的回调函数的执行是在微任务中,此时我们会发现setTimeout中的回调函数中的then方法传入的回调函数并不会被执行;
const promise = new MyPromise((resolve, reject) => {
resolve(1111)
})
setTimeout(() => {
// 此处then方法传入的回调函数并不会被执行
promise.then(value => {
console.log(value)
})
})
因此我们需要对then方法进行优化来解决这些问题:
- 采用数组结构来保存then方法传入的回调函数,并在promise对象状态确定之后遍历执行;
- 如果在执行then方法时,promise对象的状态已经确定了,就立即执行对应的回调函数;
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
class MyPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledFns = [];
this.onRejectedFns = [];
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
this.value = value;
this.status = PROMISE_STATUS_FULFILLED;
queueMicrotask(() => {
this.onFulfilledFns.forEach((fn) => {
fn(this.value);
});
});
}
};
const reject = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
this.reason = reason;
this.status = PROMISE_STATUS_REJECTED;
queueMicrotask(() => {
this.onRejectedFns.forEach((fn) => {
fn(this.reason);
});
});
}
};
executor(resolve, reject);
}
then(onFulfilled, onRejected) {
if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
onFulfilled(this.value);
}
if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
onRejected(this.reason);
}
if (this.status === PROMISE_STATUS_PENDING) {
if (onFulfilled) {
this.onFulfilledFns.push(onFulfilled);
}
if (onRejected) {
this.onRejectedFns.push(onRejected);
}
}
}
}
then方法的设计优化二
在Promises/A+规范中,还规定了then方法的返回值必须是一个promise对象:
- 如果then方法中传入的onFulfilled或onRejected方法正常执行,那么then方法将返回一个fulfilled的promise对象,并且将then方法的回调函数返回值作为value;
- 如果then方法传入的回调函数在执行的时候抛出异常,那么then方法将返回一个rejected的promise对象,并且将异常作为reason;
- 如果then方法没有对应状态的回调函数,那么then方法将返回一个相同状态的promise对象,并且使用原promise的value或reason;
因此,对我们上面的代码再进行优化:
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
class MyPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledFns = [];
this.onRejectedFns = [];
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
this.value = value;
this.status = PROMISE_STATUS_FULFILLED;
queueMicrotask(() => {
this.onFulfilledFns.forEach((fn) => {
fn(this.value);
});
});
}
};
const reject = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
this.reason = reason;
this.status = PROMISE_STATUS_REJECTED;
queueMicrotask(() => {
this.onRejectedFns.forEach((fn) => {
fn(this.reason);
});
});
}
};
executor(resolve, reject);
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
try {
const result = onFulfilled(this.value);
resolve(result);
} catch(e) {
reject(e)
}
}
if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
try {
const result = onRejected(this.reason);
resolve(result)
} catch (e) {
reject(e)
}
}
if (this.status === PROMISE_STATUS_PENDING) {
if (onFulfilled) {
this.onFulfilledFns.push((value) => {
try {
const result = onFulfilled(value)
resolve(result)
} catch (e) {
reject(e)
}
});
}
if (onRejected) {
this.onRejectedFns.push((reason) => {
execFnWithCatchErr(onRejected, reason, resolve, reject)
try {
const result = onRejected(reason)
resolve(result)
} catch (e) {
reject(e)
}
});
}
}
})
}
}
观察上面的代码,发现:
try {
const result = onRejected(reason)
resolve(result)
} catch (e) {
reject(e)
}
这样的代码总是存在,因为我们可以将它抽离成一个函数:
function execFnWithCatchErr(fn, arg, resolve, reject) {
try {
const result = fn(arg)
resolve(result)
} catch (e) {
reject(e)
}
}
从而来简化then方法中的代码:
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
function execFnWithCatchErr(fn, arg, resolve, reject) {
try {
const result = fn(arg)
resolve(result)
} catch (e) {
reject(e)
}
}
class MyPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledFns = [];
this.onRejectedFns = [];
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
this.value = value;
this.status = PROMISE_STATUS_FULFILLED;
queueMicrotask(() => {
this.onFulfilledFns.forEach((fn) => {
fn(this.value);
});
});
}
};
const reject = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
this.reason = reason;
this.status = PROMISE_STATUS_REJECTED;
queueMicrotask(() => {
this.onRejectedFns.forEach((fn) => {
fn(this.reason);
});
});
}
};
executor(resolve, reject);
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
execFnWithCatchErr(onFulfilled, this.value, resolve, reject)
}
if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
execFnWithCatchErr(onRejected, this.reason, resolve, reject)
}
if (this.status === PROMISE_STATUS_PENDING) {
if (onFulfilled) {
this.onFulfilledFns.push((value) => {
execFnWithCatchErr(onFulfilled, value, resolve, reject)
});
}
if (onRejected) {
this.onRejectedFns.push((reason) => {
execFnWithCatchErr(onRejected, reason, resolve, reject)
});
}
}
})
}
}
catch方法的实现
上面我们已经基本实现了一个Promises/A+规范的MyPromise类了,下面我们来对ES6中Promise的一些方法进行实现,它们并不在Promises/A+规范中,只是ES6为了方便我们操作做了一些补充和拓展。
我们先来分析一下catch方法的作用:
- catch方法会对一个promise对象的rejected状态进行捕获;
- catch方法同样会返回一个新的promise对象;
- 当前一个promise对象的then方法在调用时没有传入对应的onRejected回调函数时,且这个promise对象的最终状态是rejected时,可以在then方法之后链式调用catch方法来对这个rejected状态进行处理;
对应的代码实现:
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
function execFnWithCatchErr(fn, arg, resolve, reject) {
try {
const result = fn(arg);
resolve(result);
} catch (e) {
reject(e);
}
}
class MyPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledFns = [];
this.onRejectedFns = [];
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
this.value = value;
this.status = PROMISE_STATUS_FULFILLED;
queueMicrotask(() => {
this.onFulfilledFns.forEach((fn) => {
fn(this.value);
});
});
}
};
const reject = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
this.reason = reason;
this.status = PROMISE_STATUS_REJECTED;
queueMicrotask(() => {
this.onRejectedFns.forEach((fn) => {
fn(this.reason);
});
});
}
};
executor(resolve, reject);
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
// 当调用then方法时没有传入onRejected回调函数时,
// 如果这个promise最终状态为rejected,我们可以将onRejected设置为一个抛出异常的函数
// 这样它会在执行的时候被异常捕获,返回一个rejected的promise对象
if (!onRejected) {
onRejected = (reason) => {throw reason}
}
if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
execFnWithCatchErr(onFulfilled, this.value, resolve, reject);
}
if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
execFnWithCatchErr(onRejected, this.reason, resolve, reject);
}
if (this.status === PROMISE_STATUS_PENDING) {
if (onFulfilled) {
this.onFulfilledFns.push((value) => {
execFnWithCatchErr(onFulfilled, value, resolve, reject);
});
}
if (onRejected) {
this.onRejectedFns.push((reason) => {
execFnWithCatchErr(onRejected, reason, resolve, reject);
});
}
}
});
}
catch(onRejected) {
return this.then(undefined, onRejected);
}
}
const promise = new MyPromise((resolve, reject) => {
reject('err1')
})
promise.catch((reason) => {
console.log(reason);
})
promise.then(value => {
console.log(value);
}).catch(reason => {
console.log(reason);
})
// 运行结果
// err1
// err1
finally方法的实现
finally方法的作用是当一个promise对象确定最终状态之后,这个方法总是会被调用,并且finally方法中传入的回调函数不会被传入任何值(value或者reason);
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
function execFnWithCatchErr(fn, arg, resolve, reject) {
try {
const result = fn(arg);
resolve(result);
} catch (e) {
reject(e);
}
}
class MyPromise {
constructor(executor) {
this.status = PROMISE_STATUS_PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledFns = [];
this.onRejectedFns = [];
const resolve = (value) => {
if (this.status === PROMISE_STATUS_PENDING) {
this.value = value;
this.status = PROMISE_STATUS_FULFILLED;
queueMicrotask(() => {
this.onFulfilledFns.forEach((fn) => {
fn(this.value);
});
});
}
};
const reject = (reason) => {
if (this.status === PROMISE_STATUS_PENDING) {
this.reason = reason;
this.status = PROMISE_STATUS_REJECTED;
queueMicrotask(() => {
this.onRejectedFns.forEach((fn) => {
fn(this.reason);
});
});
}
};
executor(resolve, reject);
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
// 当调用then方法时没有传入onRejected回调函数时,
// 如果这个promise最终状态为rejected,我们可以将onRejected设置为一个抛出异常的函数
// 这样它会在执行的时候被异常捕获,返回一个rejected的promise对象
if (!onRejected) {
onRejected = (reason) => {throw reason}
}
// 当调用then方法时没有传入onFulfilled回调函数时,
// 如果这个promise最终状态为fulfilled时,我们可以将onFulfilled设置成一个返回参数值的函数
if (!onFulfilled) {
onFulfilled = (value) => value
}
if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
execFnWithCatchErr(onFulfilled, this.value, resolve, reject);
}
if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
execFnWithCatchErr(onRejected, this.reason, resolve, reject);
}
if (this.status === PROMISE_STATUS_PENDING) {
if (onFulfilled) {
this.onFulfilledFns.push((value) => {
execFnWithCatchErr(onFulfilled, value, resolve, reject);
});
}
if (onRejected) {
this.onRejectedFns.push((reason) => {
execFnWithCatchErr(onRejected, reason, resolve, reject);
});
}
}
});
}
catch(onRejected) {
return this.then(undefined, onRejected);
}
finally(onFinally) {
if (onFinally) {
this.then(() => {onFinally()}, () => {onFinally()})
}
}
}
const promise = new MyPromise((resolve, reject) => {
// reject(111)
resolve(111)
})
promise.then(value => {
console.log(value);
}).catch(reason => {
console.log(reason);
}).finally(() => {
console.log('总是会被执行');
})
Promise.resolve/reject方法的实现
这是两个Promise类上的静态方法:
- Promise.resolve方法的作用是将输入值转成一个状态为fulfilled的promise对象;
- Promise.reject方法的作用是将输入值转成一个状态为rejected的promise对象;
class MyPromise {
static resolve(value) {
return new MyPromise((resolve) => {
resolve(value)
})
}
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason)
})
}
...
}
MyPromise.resolve(111).then(value => {
console.log(value);
})
MyPromise.reject(222).catch(reason => {
console.log(reason);
})
Promise.all/allSettled方法的实现
- all方法会接收一个数组,这个数组中的promise状态会决定all方法返回的promise状态,当数组中所有的promise状态为fulfilled时,返回promise状态为fulfilled,并且使用数组中所有promise的value组成一个数组作为新promise的value;当数组中存在一个promise状态为rejected时,返回的promise状态也会rejected,并且使用这个promise的reason作为新promise的reason;
- all方法与all方法的区别在于它返回的promise对象状态始终为fulfilled,并且它的value会保存数组中每个promise的最终状态和值;
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
class MyPromise {
static all(promises) {
return new MyPromise((resolve, reject) => {
const values = [];
promises.forEach((promise) => {
promise.then(
(value) => {
values.push(value);
if (values.length === promises.length) {
resolve(values);
}
},
(reason) => {
reject(reason);
}
);
});
});
}
static allSettled(promises) {
return new MyPromise((resolve) => {
const values = []
promises.forEach(promise => {
promise.then(value => {
values.push({
status: PROMISE_STATUS_FULFILLED,
value
})
if (values.length === promises.length) resolve(values)
}, reason => {
values.push({
status: PROMISE_STATUS_REJECTED,
reason
})
if (values.length === promises.length) resolve(values)
})
})
})
}
...
}
const promise1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
// resolve(111);
reject(111)
}, 1000);
});
const promise2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(222);
}, 2000);
});
const promise3 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(333)
}, 3000);
});
MyPromise.all([promise1, promise2, promise3]).then(value => {
console.log(value);
}, reason => {
console.log(reason);
})
MyPromise.allSettled([promise1, promise2, promise3]).then(value => {
console.log(value);
})
// 运行结果:
// 111
// [
// { status: 'rejected', reason: 111 },
// { status: 'fulfilled', value: 222 },
// { status: 'fulfilled', value: 333 }
// ]
Promise.race/any方法的实现
- race方法会接收一个promise数组,并采用状态确定的第一个promise对象作为结果返回;
- any方法类似race方法,只是它会采用第一个状态为fulfilled的promise对象作为结果返回,如果数组中的promise对象最终状态都为rejected,则会抛出异常AggregateError;
class MyPromise {
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(promise => {
promise.then(value => {
resolve(value)
}, reason => {
reject(reason)
})
})
})
}
static any(promises) {
return new MyPromise((resolve, reject) => {
const reasons = []
promises.forEach(promise => {
promise.then(value => {
resolve(value)
}, reason => {
reasons.push(reason)
if (reasons.length === promises.length) {
reject(new AggregateError(reasons))
}
})
})
})
}
...
}