1. 明确需求
在 Promise 中,then 方法和 catch 方法都是可以通过链式调用这种形式无限调用下去的。
所以考虑以下几个改造点:
- then方法中应该直接把 this 给 return 出去(链式调用常规操作);
- 链式调用允许我们多次调用 then,多个 then 中传入的 onResolved(也叫onFulFilled) 和 onRejected 任务,我们需要把它们维护在一个队列里;
- 要想办法确保 then 方法执行的时机,务必在 onResolved 队列 和 onRejected 队列批量执行前。不然队列任务批量执行的时候,任务本身都还没收集完。一个比较容易想到的办法就是把批量执行这个动作包装成异步任务,这样就能确保它一定可以在同步代码之后执行了。
2. 编码
2.1 构造函数
function CutePromise(executor) {
// value 记录异步任务成功的执行结果
this.value = null;
// reason 记录异步任务失败的原因
this.reason = null;
// status 记录当前状态,初始化是 pending
this.status = 'pending';
// 缓存两个队列,维护 resolved 和 rejected 各自对应的处理函数
this.onResolvedQueue = [];
this.onRejectedQueue = [];
// 把 this 存下来,后面会用到
var self = this;
// 定义 resolve 函数
function resolve(value) {
// 如果不是 pending 状态,直接返回
if (self.status !== 'pending') {
return;
}
// 异步任务成功,把结果赋值给 value
self.value = value;
// 当前状态切换为 resolved
self.status = 'resolved';
// 用 setTimeout 延迟队列任务的执行
setTimeout(function(){
// 批量执行 resolved 队列里的任务
self.onResolvedQueue.forEach(resolved => resolved(self.value));
});
}
// 定义 reject 函数
function reject(reason) {
// 如果不是 pending 状态,直接返回
if (self.status !== 'pending') {
return;
}
// 异步任务失败,把结果赋值给 value
self.reason = reason;
// 当前状态切换为 rejected
self.status = 'rejected';
// 用 setTimeout 延迟队列任务的执行
setTimeout(function(){
// 批量执行 rejected 队列里的任务
self.onRejectedQueue.forEach(rejected => rejected(self.reason));
});
}
// 把 resolve 和 reject 能力赋予执行器
try{
execute(resolve, reject)
}catch(e){
reject(e)
}
}
2.2 then 方法
// then 方法接收两个函数作为入参(可选)
CutePromise.prototype.then = function(onResolved, onRejected) {
// 注意,onResolved 和 onRejected必须是函数;如果不是,我们此处用一个透传来兜底
if (typeof onResolved !== 'function') {
onResolved = function(x) {return x};
}
if (typeof onRejected !== 'function') {
onRejected = function(e) {throw e};
}
// 依然是保存 this
var self = this;
// 判断是否是 resolved 状态
if (self.status === 'resolved') {
// 如果是 执行对应的处理方法
onResolved(self.value);
} else if (self.status === 'rejected') {
// 若是 rejected 状态,则执行 rejected 对应方法
onRejected(self.reason);
} else if (self.status === 'pending') {
// 若是 pending 状态,则只对任务做入队处理
self.onResolvedQueue.push(onResolved);
self.onRejectedQueue.push(onRejected);
}
return this
};
3. 测试
const cutePromise = new CutePromise(function (resolve, reject) {
resolve('成了!');
});
cutePromise.then((value) => {
console.log(value)
console.log('我是第 1 个任务')
}).then(value => {
console.log('我是第 2 个任务')
});
// 依次输出“成了!” “我是第 1 个任务” “我是第 2 个任务”