优点
链式调用、解决回调地狱问题
一、最简版
// 定义状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class SelfPromise {
// 储存状态,初始值是 pending
status = PENDING;
// 成功之后的值
value = null;
// 失败之后的原因
reason = null;
constructor(executor) {
// 将 resolve 和 reject 传给 new Promsie 的回调函数
executor(this.resolve, this.reject);
}
// 箭头函数可以使函数里面的 this 始终指向 Promise 实例对象
resolve = (value) => {
// 只有状态是 pending 的情况下,才改变为 fulfilled 状态
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
}
};
reject = (reason) => {
// 只有状态是 pending 的情况下,才改变为 rejected 状态
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
}
};
}
这里有两个注意点:
resolve和reject方法只有是在箭头函数的情况下,才能直接传递给executor函数作为参数(executor(this.resolve, this.reject)),这样在外部调用resolve或reject函数的时候,它们的this指向始终是Promise实例对象。那改为普通函数可不可以呢?也可以,不过就是需要使用bind方法来稳定resolve和reject的this指向 —executor(this.resolve.bind(this), this.reject.bind(this))。resolve和reject只有在pending状态下,才需要改变状态和记录结果,这样就达到了Promise状态一旦改变就不能再改变的效果。
二、添加then
// 定义状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class SelfPromise {
// 储存状态,初始值是 pending
status = PENDING;
// 成功之后的值
value = null;
// 失败之后的原因
reason = null;
constructor(executor) {
// 将 resolve 和 reject 传给 new Promsie 的回调函数
executor(this.resolve, this.reject);
}
// 箭头函数可以函数里面的 this 始终指向 Promise 实例对象
resolve = (value) => {
// 只有状态是 pending 的情况下,才改变为 fulfilled 状态
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
}
};
reject = (reason) => {
// 只有状态是 pending 的情况下,才改变为 rejected 状态
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
}
};
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
// 把 resolve 的值传递给 fulfilled 状态的回调函数,并且调用它。
onFulfilled(this.value);
} else if (this.status === REJECTED) {
// 把 reject 的值传递给 rejected 状态的回调函数,并且调用它。
onRejected(this.reason);
}
}
}
三、处理异步逻辑
如果在 then 方法中判断到状态是 pending,那么就先将两个回调函数保存起来,然后在 Promise 内部的 resolve 或 reject 方法中执行。
//... 省略部分代码
class SelfPromise {
+ // 保存 onFulfilled 回调函数
+ onFulfilledCallback = null;
+ // 保存 onRejected 回调函数
+ onRejectedCallback = null;
//... 省略部分代码
resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
+ // 执行 onFulfilled 回调函数
+ this.onFulfilledCallback && this.onFulfilledCallback(value);
}
};
reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
+ // 执行 onRejected 回调函数
+ this.onRejectedCallback && this.onRejectedCallback(reason);
}
};
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value);
} else if (this.status === REJECTED) {
onRejected(this.reason);
+ } else {
+ // pending 状态下保存回调函数
+ this.onFulfilledCallback = onFulfilled;
+ this.onRejectedCallback = onRejected;
}
}
}
四、then 方法的多次调用
const p = new SelfPromise((resolve, reject) => {
setTimeout(() => {
resolve("fulfilled");
});
});
p.then((value) => {
console.log("1", value);
});
p.then((value) => {
console.log("2", value);
});
p.then((value) => {
console.log("3", value);
});
// 3 fulfilled
目前的代码只能输出 3 fulfilled,需要把 1 fulfilled 和 2 fulfilled 的输出补上。至于为什么只能输出 3 fulfilled 呢?关键在于源码当中 then 方法保存回调函数的方式, 应该用两个数组来保存所有的回调函数,同时 Promise 内部的 resolve 和 reject 方法也需要循环调用所有的回调函数。
//... 省略部分代码
class SelfPromise {
// 保存所有的 onFulfilled 回调函数
+ onFulfilledCallbacks = [];
+ // 保存所有的 onRejected 回调函数
+ onRejectedCallbacks = [];
//... 省略部分代码
resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
+ // 执行所有的 onFulfilled 回调函数
+ this.onFulfilledCallbacks.forEach((fn) => fn(value));
}
};
reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
+ // 执行 onRejected 回调函数
+ this.onRejectedCallbacks.forEach((fn) => fn(reason));
}
};
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value);
} else if (this.status === REJECTED) {
onRejected(this.reason);
+ } else {
+ // pending 状态下保存所有的回调函数
+ this.onFulfilledCallbacks.push(onFulfilled);
+ this.onRejectedCallbacks.push(onRejected);
}
}
}
现在能输出预期的结果了.
细心的朋友应该发现了这其实是一个发布订阅模式,then 方法的 this.onFulfilledCallbacks.push(onFulfilled) 和 this.onRejectedCallbacks.push(onRejected) 就是在添加订阅者,而 resolve 和 reject 方法就是在通知所有的订阅者。
五、then 方法的链式调用
要想实现 then 方法的链式调用,then 方法必须返回 Promise 对象,并且下一个 then 方法的回调函数的参数会依赖上一个 then 方法的回调函数的返回值,这种依赖有两种情况:
-
如果返回的是
Promise实例对象,那么下一个then方法的回调函数会接收该实例对象的resolve或reject函数传入的值,比如:const p = new Promise((resolve, reject) => { resolve(1); }); p.then((value) => { console.log(value); return new Promise((resolve, reject) => { resolve(2); // reject(2); }); }).then((value) => { console.log("fulfilled", value); }, (err) => { console.log("rejected", err); }); // 1 // fulfilled 2 // 如果调用的是 reject(2),那么返回的是: // 1 // rejected 2 -
如果返回的是
thenable对象,会和第一种情况一样:const p = new Promise((resolve, reject) => { resolve(1); }); p.then((value) => { console.log(value); return { then(resolve, reject) { resolve(2); // reject(2); } }; }).then((value) => { console.log("fulfilled", value); }, (err) => { console.log("rejected", err); }); // 1 // fulfilled 2 // 如果调用的是 reject(2),那么返回的是: // 1 // rejected 2 -
如果返回的是其他对象或者原始数据类型的值,那么下一个
then方法的回调函数的参数会直接接收这个值,比如:const p = new Promise((resolve, reject) => { resolve(1); }); p.then((value) => { console.log(value); return { value: 2, }; }).then((value) => { console.log("fulfilled", value); }); // 1 // fulfilled { value: 2 }
接着,通过 resolve 和 reject 函数来改变 promise2 对象的状态,并且建立上一个 then 方法与下一个 then 方法的依赖关系。完整代码:
class SelfPromise {
// ... 省略部分代码
then(onFulfilled, onRejected) {
const promise2 = new SelfPromise((resolve, reject) => {
if (this.status === FULFILLED) {
+ // 获取上一个 then 方法的 fulfilled 回调函数的返回值
+ const v = onFulfilled(this.value);
+ // 根据返回值,改变 promise2 的状态,并建立与下一个 then 方法的关系
+ resolvePromise(v, resolve, reject);
} else if (this.status === REJECTED) {
+ // 获取上一个 then 方法的 rejected 回调函数的返回值
+ const v = onRejected(this.reason);
+ //根据返回值,改变 promise2 的状态,并建立与下一个 then 方法的关系
+ resolvePromise(v, resolve, reject);
} else {
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
});
return promise2;
}
}
function resolvePromise(value, resolve, reject) {
if (typeof value === "object" || typeof value === "function") {
if (value === null) {
// 如果返回值是 null,
// 直接调用 resolve 函数,promise2 的状态变为 fulfilled,
// 返回值由下一个 then 方法的第一个回调函数接收。
return resolve(value);
}
try {
if (typeof value.then === "function") {
// 如果返回值是 Promise 对象或者 thenable 对象
// 那就只能交给它们的 then 方法来改变 promise2 的状态,以及获取相对应的状态值
// 以下代码等同于 value.then((value) => resolve(value), (err) => reject(err))
value.then(resolve, reject);
} else {
// 如果 then 不是函数,同 null 情况一样的处理逻辑。
resolve(value);
}
} catch (error) {
// 出现异常的情况下,调用 reject 函数
// promise2 的状态变为 rejected,
// 错误信息由下一个 then 方法的第二回调函数接收
reject(error);
}
} else {
// 如果返回值是其他对象或者原始数据类型值,同 null 情况一样的处理逻辑。
resolve(value);
}
}
六、Promise 循环调用
我们来看看原生的 Promise 是怎么处理循环调用的:
const p = new Promise((resolve, reject) => {
resolve(1);
});
const p1 = p.then(value => {
console.log(value);
return p1;
});
// 1
// Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
报错,错误信息是:会发生 Promise 循环调用。
所以,我们需要改造一下 SelfPromise 的代码,来模拟这种报错的效果:
function resolvePromise(promise2, value, resolve, reject) {
+ // 如果 then 方法返回的是自身 Promise 对象,返回错误信息
+ if (promise2 === value) {
+ return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
+ }
if (typeof value === "object" || typeof value === "function") {
if (value === null) {
return resolve(value);
}
try {
if (typeof value.then === "function") {
value.then(resolve, reject);
} else {
resolve(value);
}
} catch (error) {
reject(error);
}
} else {
resolve(value);
}
}
class SelfPromise {
// ... 省略部分代码
then(onFulfilled, onRejected) {
const promise2 = new SelfPromise((resolve, reject) => {
if (this.status === FULFILLED) {
const v = onFulfilled(this.value);
+ // 将 promise2 传入进行判断
+ resolvePromise(promise2, v, resolve, reject);
} else if (this.status === REJECTED) {
const v = onRejected(this.reason);
+ // 将 promise2 传入进行判断
+ resolvePromise(promise2, v, resolve, reject);
} else {
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
});
return promise2;
}
}
再测试一下前面的例子,得出结果:
// 1
// Uncaught ReferenceError: Cannot access 'p1' before initialization
尴尬,错误信息完全不一样,这里根据提示可以知道,我们在 p1 定义之前就使用了它。实际情况也确实如此,我们是先等 then 方法里面的回调函数执行完毕之后,then 方法再返回 Promise 对象,但我们却在回调函数内先用了这个 Promise 对象,所以才报的这个错误信息。
那怎么办呢?其实只需要把 then 方法的回调函数的同步执行改为异步执行就可以了。对原生 Promise 足够了解的朋友应该知道,then 方法的回调函数是微任务,创建微任务的方式有以下这几种:
- 浏览器环境下有
MutationObserver。 Promise.then()- Node 环境下有
process.nextTick。 queueMicrotask
由于 Promise.then 是我们自己要手动实现的,MutationObserver 和 process.nextTick 又需要在专门的环境下使用,所以这里选择使用 queueMicrotask 来实现微任务的创建。
可以在 MDN 文档上了解更多关于 queueMicrotask 的信息。
我们再来改造一下 SelfPromise 的代码:
class SelfPromise {
// ... 省略部分代码
then(onFulfilled, onRejected) {
const promise2 = new SelfPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
queueMicrotask(() => {
const v = onFulfilled(this.value);
resolvePromise(promise2, v, resolve, reject);
});
};
const rejectedMicrotask = () => {
queueMicrotask(() => {
const v = onRejected(this.reason);
resolvePromise(promise2, v, resolve, reject);
});
};
if (this.status === FULFILLED) {
// 异步执行 fulfilled 回调函数
fulfilledMicrotask();
} else if (this.status === REJECTED) {
// 异步执行 rejected 回调函数
rejectedMicrotask();
} else {
// 添加订阅者(异步执行的回调函数)
this.onFulfilledCallbacks.push(fulfilledMicrotask);
this.onRejectedCallbacks.push(rejectedMicrotask);
}
});
return promise2;
}
}
测试一下:
const p = new SelfPromise((resolve, reject) => {
resolve('1')
})
// 返回自身 Promise 对象
const p1 = p.then(value => {
console.log(value);
return p1;
})
// 接收错误信息
p1.then(value => {
console.log(2);
console.log('fulfilled', value)
}, err => {
console.log(3);
console.log('reject', err.message);
})
// 1
// 3
// reject Chaining cycle detected for promise #<Promise>
效果终于达到了!
七、静态方法 resolve 和 reject 的实现
静态 resolve 方法的实现
我们经常使用 Promise.resolve() 来生成一个状态是 fulfilled 的 Promise 对象,该方法的参数有三种:
1.如果参数是 Promise 实例对象,那么 Promise.resolve 方法将原封不动地返回这个对象。
2.如果参数是 thenable 对象,那么在 Promise.resolve 内部会将该对象的 then 方法放入微任务队列中执行。比如:
const thenable = {
then(resolve, reject) {
console.log(1);
resolve(2);
}
};
Promise.resolve(thenable).then((value) => {
console.log(value);
});
console.log(3);
// 3
// 1
// 2
- 不传参数或者参数是其他类型的数据,
Promise.resolve方法返回fulfilled状态的Promise实例对象。
基于以上的特点,我们来实现一下它的原理:
// ...省略部分代码
class SelfPromise {
// ...省略部分代码
static resolve(param) {
// 如果参数是 Promise 实例对象,原封不动地返回这个对象
if (param instanceof SelfPromise) {
return param;
}
return new SelfPromise((resolve, reject) => {
if (param && typeof param.then === "function") {
// 如果参数是 thenable 对象,放入微任务队列中执行
queueMicrotask(() => {
param.then(resolve, reject);
});
} else {
// 其他情况直接调用 resolve 函数,返回 fulfilled 状态的 Promise 对象
resolve(param);
}
});
}
}
静态 reject 方法的实现
Promise.reject 方法也是一个 Promise 对象,状态是 rejected,但它的参数类型并没有 Promise.resolve 方法那么复杂,只有一种情况:无论传入什么类型的数据,都只会返回 rejected 状态的 Promise 实例对象。
实现原理也比较简单:
js
复制代码
// ...省略部分代码
class SelfPromise {
// ...省略部分代码
static reject(param) {
return new SelfPromise((resolve, reject) => {
reject(param);
});
}
}
八、catch 方法实现
catch 方法是用于指定发生错误时的回调函数,它其实就是对 then 方法的调用,想想我们之前是通过 then 方法的第二个参数来接收 rejected 状态的错误:
const p = new SelfPromise((resolve, reject) => {
reject(1);
})
p.then((value) => {
console.log("fulfilled", value);
}, (reason) => {
console.log("rejected", reason);
});
// rejected 1
所以, catch 方法等同于 then(null, onRejected) 或 then(undefined, onRejected),因此实现原理也很明了:
// ...省略部分代码
class SelfPromise {
// ...省略部分代码
catch(onRejected) {
return this.then(null, onRejected);
}
}
九、finally 方法实现
finally 方法用于在 Promise 对象的状态是 fulfilled 还是 rejected 的情况下,都会执行的操作。注意,finally 方法的回调函数不接受任何参数,同时,finally 方法也会返回 Promise 对象。
finally 中文翻译过来是最终的意思,所以,finally 方法的回调函数无论返回什么值,都不会传递给后面链式调用的 then 方法的回调函数。并且,finally 方法自带值穿透特性,会将前面 then 方法回调函数返回的值自动传给它后面的 then 方法或者 catch 方法的回调函数。比如:
Promise.reject(1)
.finally((reason) => {
console.log("finally", reason);
return 2;
})
.then((value) => {
console.log("then", value);
})
.catch((reason) => {
console.log("catch", reason);
});
// finally undefined
// catch 1
finally 本质上也是 then 方法的特例,由于 finally 方法的回调函数跟前面 Promise 对象的状态无关,所以,就等同于需要在 then 方法中的两个回调函数里调用 finally 方法的回调函数,并且基于 finally 自身的一些特性,还需要借助静态方法 resolve 来实现其原理:
// ...省略部分代码
class SelfPromise {
// ...省略部分代码
finally(callback) {
return this.then(
// 值穿透以及 callback() 返回值不会传递给后面 then 方法的原理
(value) => SelfPromise.resolve(callback()).then(() => value),
(reason) => SelfPromise.resolve(callback()).then(() => { throw reason; })
);
}
}
十、静态方法 all,race,allSettled 和 any 实现
Promise.all 的实现
Promise.all 方法接收一个具有 Iterator 接口的数据作为参数,参数中的每一个元素都是 Promise 对象,如果不是,就会用 Promise.resolve 方法将它转换为 Promise 对象。
Promise.all 方法返回的也是一个新的 Promise 对象,当所有元素的状态都是 fulfilled 时,返回的新 Promise 对象的状态才是 fulfilled,否则就是 rejected。
如果新 Promise 对象的状态是 fulfilled,那么用 then 方法接收的结果是一个数组,数组中结果的顺序就是 Promise.all 方法参数的顺序,比如:
Promise.all([
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3),
]).then((res) => {
console.log(res);
});
// [1, 2, 3]
原理实现思路:
- 首先要判断参数的
Symbol.iterator属性是否是函数,如果是,这表明参数是具有Iterator接口的数据,如果不是直接返回错误。 - 统一遍历具有
Iterator接口的数据的方法,因为需要考虑到Set,Map,String等类型数据。 - 当每个
Promise对象元素的状态变为fulfilled时,会将其结果存到对应索引的数组中。
// ...省略部分代码
class SelfPromise {
// ...省略部分代码
static all(promiseIterator) {
return new SelfPromise((resolve, reject) => {
// 判断参数是否是具有 `Iterator` 接口的数据
if (
promiseIterator &&
typeof promiseIterator[Symbol.iterator] === "function"
) {
const res = []; // 结果数组
let countRes = 0; // 记录数组中结果的个数
const len = promiseIterator.length || promiseIterator.size;
// 保存对应索引的结果
function saveRes(value, index) {
res[index] = value;
if (++countRes === len) {
resolve(res);
}
}
// 返回迭代器对象
const iterator = promiseIterator[Symbol.iterator]();
// 遍历具有迭代器的数据结构,并且记录索引值
for (
let i = 0, iteratorRes = iterator.next();
iteratorRes.done !== true;
i++, iteratorRes = iterator.next()
) {
SelfPromise.resolve(iteratorRes.value).then((value) => {
// 在对应索引位置上保存结果
saveRes(value, i);
}, reject);
}
} else {
reject(new TypeError("Arguments is not iterable"));
}
});
}
}
Promise.race 的实现原理
Promise.race 方法接收的参数和返回值同 Promise.all 一样,它的特点是:哪个 Promise 实例对象的状态改变得快,Promise.race 方法最后就是什么状态,并且那个率先改变状态的 Promise 实例对象的返回值,会传递给 Promise.race 方法返回值的 then 方法或 catch 方法。
举个例子:
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(2);
}, 2000);
});
Promise.race([p1, p2]).then((res) => {
console.log(res);
});
// 一秒过后,输出 1,状态为 fulfilled
实现原理跟 Promise.all 方法差不多,只不过不再需要把结果保存到数组上了。
// ...省略部分代码
class SelfPromise {
// ...省略部分代码
static race(promiseIterator) {
return new SelfPromise((resolve, reject) => {
if (
promiseIterator &&
typeof promiseIterator[Symbol.iterator] === "function"
) {
// 返回迭代器对象
const iterator = promiseIterator[Symbol.iterator]();
// 遍历具有迭代器的数据结构
for (
let iteratorRes = iterator.next();
iteratorRes.done !== true;
iteratorRes = iterator.next()
) {
// 哪个 Promise 对象状态改变得快,race 方法最后就是什么状态
SelfPromise.resolve(iteratorRes.value).then(
resolve,
reject
);
}
} else {
reject(new TypeError("Arguments is not iterable"));
}
});
}
}
Promise.allSettled 的实现原理
Promise.allSettled 方法接收的参数同 Promise.all 一样,不过 Promise.allSettled 方法只有在所有 Promise 对象都发生状态改变了(无论是 fulfilled 还是 rejected),返回的新 Promise 实例对象状态才会改变,并且状态总是为 fulfilled。
实现原理和 Promise.all 方法基本一样,只不过在每个 Promise 对象上的 rejected 状态的回调函数处理不同。
// ...省略部分代码
class SelfPromise {
// ...省略部分代码
static allSettled(promiseIterator) {
return new SelfPromise((resolve, reject) => {
if (
promiseIterator &&
typeof promiseIterator[Symbol.iterator] === "function"
) {
const res = [];
let countRes = 0;
const len = promiseIterator.length || promiseIterator.size;
function saveRes(value, index) {
res[index] = value;
if (++countRes === len) {
resolve(res);
}
}
// 返回迭代器对象
const iterator = promiseIterator[Symbol.iterator]();
// 遍历具有迭代器的数据结构,并且记录索引值
for (
let i = 0, iteratorRes = iterator.next();
iteratorRes.done !== true;
i++, iteratorRes = iterator.next()
) {
SelfPromise.resolve(iteratorRes.value)
.then((value) => {
saveRes({ status: "fullfilled", value }, i);
})
.catch((reason) => {
saveRes({ status: "rejected", reason }, i);
});
}
} else {
reject(new TypeError("Arguments is not iterable"));
}
});
}
}
Promise.any 的实现原理
Promise.any 方法接收的参数同 Promise.all 一样,只要其中一个 Promise 实例对象的状态变成 fulfilled,那么Promise.any 方法返回的新 Promise 实例对象的状态就是 fulfilled,如果所有 Promise 实例对象都变成 rejected 状态,返回的新 Promise 实例对象的状态才会变成 rejected 状态。
在实现原理上,有一点是跟 Promise.all 方法反过来的,那就是每个 Promise 实例对象在 rejected 状态才保存对应索引位置上的结果。
// ...省略部分代码
class SelfPromise {
// ...省略部分代码
static any(promiseIterator) {
return new SelfPromise((resolve, reject) => {
if (
promiseIterator &&
typeof promiseIterator[Symbol.iterator] === "function"
) {
const res = [];
let countRes = 0;
const len = promiseIterator.length || promiseIterator.size;
function saveRes(reason, index) {
res[index] = reason;
if (++countRes === len) {
const err = new AggregateError(
res,
"All promises were rejected"
);
reject(err);
}
}
// 返回迭代器对象
const iterator = promiseIterator[Symbol.iterator]();
// 遍历具有迭代器的数据结构,并且记录索引值
for (
let i = 0, iteratorRes = iterator.next();
iteratorRes.done !== true;
i++, iteratorRes = iterator.next()
) {
SelfPromise.resolve(iteratorRes.value).then(
resolve,
(reason) => {
// 在对应索引位置上保存结果
saveRes(reason, i);
}
);
}
} else {
reject(new TypeError("Arguments is not iterable"));
}
});
}
}
总得来说,实现 Promise 的原理关键在于:
Promise运用了发布订阅模式,then方法用于添加订阅者,resolve和reject函数用于通知所有订阅者。then方法的回调函数是异步执行的,属于微任务,所以对于thenable对象中then方法放在Promise内部执行也是异步的。then链式调用的功能在于then方法的返回值是Promise对象,对于then方法回调函数的返回值类型要有不同的处理方式。- 静态方法
resolve和reject就是封装了创建Promise对象的创建过程;实例方法catch和finally就是对then方法的二次封装;静态方法all,race,allSettled,any也是利用了then方法的机制。
参考文章链接: 我终于真正理解 Promise 了!