前言
之前发过一篇Promise的手写文章,但隔了一段时间之后发现自己又忘了怎么写,于是下决心重新梳理,这次我真的会写了。
Promise做了什么:面向对象写回调
将执行任务和设置回调相分离
Promise通过对象方法,将执行任务和挂载回调的代码相分离,解决了回调嵌套
的问题。
- new Promise():立即执行任务并激活回调(激活回调是将回调放入微任务队列,实现异步特性)
- Promise.then():实现回调挂载
const p = new Promise((resolve, reject) => {
console.log('task'); // 执行任务
resolve('data'); // 激活回调
});
p.then((v) => console.log(v)); // 挂载回调
既然如此,对象中就需要两个属性:
- 执行结果:传递给回调函数
- 回调函数:记录挂载的回调
区分正常处理回调和异常处理回调
传统的回调函数中,经常会在第一个入参传递一个e
表示error,比如function(e, data){}
,我们得自己判断e
是否为空来确定执行正常回调还是异常回调。
Promise把这一个回调拆成正常处理回调和异常处理回调两个回调,分别挂载。
p.then(
(v) => console.log(v),
(e) => console.log(e)
);
既然如此,对象中还需要一个属性:
- 执行状态:记录任务是否成功,以判断执行哪个回调
链式调用
Promise.then()将返回新的Promise,实现链式调用。
p.then((v) => v).then((v) => v);
最简版Promise
根据上面的理解,我们就可以写一个最简版的Promise,跟我念口诀:“三个属性存状态,两调用和两挂载”。
- 三个属性:status, result, callbacks
- 两调用:立即执行函数中的resolve和reject
- 两挂载:实例方法then和catch
class Promise {
constructor(executor) {
// 三个属性
this.status = 'pending'; // 状态
this.result = null; // 结果
this.callbacks = []; // 回调
// 两调用:resolve
const resolve = (data) => {
// 修改状态
this.status = 'fulfilled';
// 修改结果
this.result = data;
// 激活回调
this.callbacks.forEach((cb) => {
cb.onResolved();
});
};
// 两调用:reject
const reject = (error) => {
// 修改状态
this.status = 'rejected';
// 修改结果
this.result = error;
// 激活回调
this.callbacks.forEach((cb) => {
cb.onRejected();
});
};
// 立即执行函数
executor(resolve, reject);
}
// 两挂载:then
then(onResolved, onRejected) {
// Promise.then返回Promise
return new Promise((resolve, reject) => {
// 回调函数封装:把任务放入微任务队列
const callback = (func) => {
setTimeout(() => {
try {
const result = func(this.result);
resolve(result);
} catch (error) {
reject(error);
}
});
};
// 回调函数生成
const wrapper = (func) => {
return () => {
callback(func);
};
};
// 挂载回调函数
this.callbacks.push({
onResolved: wrapper(onResolved),
onRejected: wrapper(onRejected),
});
});
}
// 两挂载:catch
catch(onRejected) {
// then的快捷方式
return this.then(undefined, onRejected);
}
}
添加防御代码
最简版Promise有很多不完善的地方,接下来就需要对各处漏洞添加防御代码。
Promise状态只能修改一次
constructor(executor) {
// ...
const resolve = (data) => {
// 已修改的状态不能再次修改
if (this.status !== 'pending') return;
// ...
};
const reject = (error) => {
// 已修改的状态不能再次修改
if (this.status !== 'pending') return;
// ...
};
// ...
}
resolve中data如果是Promise需要等待执行结果
constructor(executor) {
// ...
const resolve = (data) => {
// 如果data是Promise,则等待执行结果
if (data instanceof Promise) {
data.then(resolve, reject);
} else {
// 修改状态
this.status = "fulfilled";
// 修改结果
this.result = data;
// 激活回调
this.callbacks.forEach((cb) => {
cb.onResolved();
});
}
};
// ...
}
立即执行函数如果异常需要reject
constructor(executor) {
//...
// 立即执行函数
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then需要判断入参是否是函数
then(onResolved, onRejected) {
// 判断入参是否为函数(防御代码)
if (typeof onResolved !== 'function') {
onResolved = (value) => value;
}
if (typeof onRejected !== 'function') {
onRejected = (error) => {
throw error;
};
}
// ...
}
then回调任务如果返回Promise需要等待结果
const callback = (func) => {
setTimeout(() => {
try {
const result = func(this.result);
// 如果回调返回Promise则等待结果
if (result instanceof Promise) {
result.then(
(value) => resolve(value),
(error) => reject(error)
);
} else {
resolve(result);
}
} catch (error) {
reject(error);
}
});
};
如果Promise状态已经变化,则then中立刻将回调加入微任务队列
then(onResolved, onRejected) {
// ...
// 返回Promise
return new Promise((resolve, reject) => {
// ...
// 如果Promise状态已经变化,则then中立刻将回调加入微任务队列
if (this.status === 'fulfilled') {
setTimeout(() => {
callback(onResolved);
});
}
if (this.status === 'rejected') {
setTimeout(() => {
callback(onRejected);
});
}
if (this.status === 'pending') {
// 挂载回调
this.callbacks.push({
onResolved: wrapper(onResolved),
onRejected: wrapper(onRejected),
});
}
});
}
四个静态方法
Promise中还有四个静态方法,都用来创建Promise,但是Promise结果的判断规则不同。
static resolve(data) {}
static reject(data) {}
static all(promises) {}
static race(promises) {}
resolve
首先是resolve,这个方法用于将数据包装为Promise,如果传入的数据是Promise则直接返回,否则返回一个将入参作为结果的成功状态的Promise。
static resolve(data) {
// 如果入参是Promise则直接返回
if (data instanceof Promise) {
return data;
// 如果入参不是Promise则包装为Promise
} else {
return new Promise((resolve, reject) => {
resolve(data);
});
}
}
reject
再来是reject,该方法不论入参是不是Promise,均返回一个失败的Promise,并且值就是传入的数据。
static reject(data) {
return new Promise((resolve, reject) => {
reject(data);
});
}
all
接下来是all,all方法用于判断多个promise是否均为成功,如果多个promise都成功则返回所有传入promise的返回值数组;如果有一个promise失败,则返回该失败的promise的返回值。
static all(promises) {
return new Promise((resolve, reject) => {
let res = [];
for (let i = 0; i < promises.length; i++) {
promises[i].then(
(value) => {
// 要保证返回的结果和传入的Promise位置一一对应
res[i] = value;
if (res.length === promises.length) {
resolve(res);
}
},
(error) => {
reject(error);
}
);
}
});
}
race
最后是race,该方法用于多个promise竞争执行,如果其中一个promise成功,则返回该成功promise的返回值;如果其中一个promise失败,则返回该失败promise的返回值。
static race(promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(
(value) => {
resolve(value);
},
(error) => {
reject(error);
}
);
}
});
}
尾声
Promise的核心就是利用面向对象将“调用回调和挂载回调”的代码分离,理解了这点就会写了。