这篇文章通过自己实现一个Promise
来加深对promise的理解,具体如何实现符合promise
规范的代码,可以查看Promises/A+
梳理 Promise 功能
1、Promise
是一个构造函数,它接收一个执行函数作为参数,执行函数里面包含resolve
和reject
两个方法;
2、调用resolve
方法表示成功,reject
方法表示失败,结果会在实例的then
函数中以参数函数的方式拿到;
3、then
是实例方法,参数包含成功的回调函数和失败的回调函数,只用resolve
或reject
执行后方法才会执行;
状态变化
promise
里面的状态决定了当前能执行什么方法,先定义三个变量来保存状态:
// 等待,用户还没调用resolve或者reject
const PENDING_STATUS = "pending";
// 成功,用户调用了resolve
const FULFILLED_STATUS = "fulfilled";
// 失败,用户调用了reject
const REJECTED_STATUS = "rejected";
有状态就会有出现状态的原因了,后面还需要把这些原因传给then
方法,我们先创建构造函数:
function Promise(executor) {
this.status = PENDING_STATUS;
// 保存resolve方法的参数
this.value = null;
// 保存reject方法的参数
this.reason = null;
}
定义好了状态我们还要知道什么时候去修改它,我们知道executor
执行函数接受resolve
和reject
两个方法,这两个方法执行的时候就是我们修改状态的时候了:
function Promise(executor) {
this.status = PENDING_STATUS;
// 保存resolve方法的参数
this.value = null;
// 保存reject方法的参数
this.reason = null;
const resolve = (value) => {
// 如果状态不是 pending 返回
if (this.status !== PENDING_STATUS) return;
// 先保存参数
this.value = value;
// 改变状态
this.status = FULFILLED_STATUS;
};
const reject = (reason) => {
if (this.status !== PENDING_STATUS) return;
this.reason = reason;
this.status = REJECTED_STATUS;
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then 方法
我们知道then
方法接受两个参数,成功后的函数回调和失败后的函数回调,那么我们可以在函数里面判断当前状态,并执行对应的方法:
Promise.prototype.then = function (onfulfilled, onrejected) {
+ if (this.status === FULFILLED_STATUS) {}
+ if (this.status === REJECTED_STATUS) {}
+ if (this.status === PENDING_STATUS) {}
};
根据状态去判断执行onfulfilled
还是onrejected
方法比较容易,但是如果状态仍处于 pending
,就不能直接执行onfulfilled
或onrejected
了,我们可以利用发布订阅的方式将方法缓存起来,直到resolve
或reject
被调用才取出来执行,我们先在构造函数里面定义onResolvedCallback
和onRejectedCallback
两个数组:
function Promise(executor) {
+ this.onResolvedCallback = [];
+ this.onRejectedCallback = [];
}
then
方法里面,我们在pending
状态时先将执行函数保存起来:
Promise.prototype.then = function (onfulfilled, onrejected) {
if (this.status === FULFILLED_STATUS) {
onfulfilled(this.value);
}
if (this.status === REJECTED_STATUS) {
onrejected(this.reason);
}
if (this.status === PENDING_STATUS) {
this.onResolvedCallback.push(() => {
onfulfilled(this.value);
});
this.onRejectedCallback.push(() => {
onrejected(this.reason);
});
}
};
在resolve
和reject
函数中取出并执行:
const resolve = (value) => {
if (this.status !== PENDING_STATUS) return;
this.value = value;
this.status = FULFILLED_STATUS;
// 取出缓存的方法并执行
+ this.onResolvedCallback.forEach((fn) => fn());
};
链式调用
promise
方法可以实现链式调用,上一个函数onfulfilled
的返回结果,可以在下一次then
函数中被获取:
new Promise((resolve) => resolve("result1"))
.then((r) => `result2`)
.then((r) => `result3`)
.then((r) => `result4`);
可以看到then
方法可以不断的被链式调用,这说明then
方法也返回了一个promise
实例:
Promise.prototype.then = function (onfulfilled, onrejected) {
let promise = new Promise((resolve, reject) => {
if (this.status === FULFILLED_STATUS) {
onfulfilled(this.value);
}
if (this.status === REJECTED_STATUS) {
onrejected(this.reason);
}
if (this.status === PENDING_STATUS) {
this.onResolvedCallback.push(() => {
onfulfilled(this.value);
});
this.onRejectedCallback.push(() => {
onrejected(this.reason);
});
}
});
return promise;
};
但是onfulfilled
和onrejected
的返回结果,有可能是普通的值或者是promise
,所以我们也要处理下返回的值:
let promise = new Promise((resolve, reject) => {
if (this.status === FULFILLED_STATUS) {
let value = onfulfilled(this.value);
// 函数在setTimeout里面执行,这样能保证拿到返回的promise变量
setTimeout(() => {
try {
resolvePromise(promise, value, resolve, reject);
} catch (error) {
onrejected(error);
}
});
}
if (this.status === REJECTED_STATUS) {
let value = onrejected(this.reason);
setTimeout(() => resolvePromise(promise, value, resolve, reject));
}
if (this.status === PENDING_STATUS) {
this.onResolvedCallback.push(() => {
let value = onfulfilled(this.value);
setTimeout(() => {
try {
resolvePromise(promise, value, resolve, reject);
} catch (error) {
onrejected(error);
}
});
});
this.onRejectedCallback.push(() => {
let value = onrejected(this.reason);
setTimeout(() => resolvePromise(promise, value, resolve, reject));
});
}
});
resolvePromise
函数我们要做的处理是判断返回的value
值,这里有几个判断需要注意:
value
跟返回的promise
相同,循环引用直接抛出错误value
是Promise
实例,在then
方法里面执行resolve
- 直接
resolve
value
function resolvePromise(promise, value, resolve, reject) {
if (promise === value) throw new Error("循环引用");
if (value instanceof Promise) {
let then = value.then;
try {
then.call(
value,
(result) => {
resolve(result);
},
(error) => {
reject(error);
}
);
} catch (error) {
reject(error);
}
} else {
resolve(value);
}
}
值的穿透
Promise
支持一种比较奇葩的写法:
let p = new Promise((resolve) => resolve("result"));
p.then()
.then()
.then((result) => console.log(result));
遇到这种写法,需要在then
函数里面提前做判断:
Promise.prototype.then = function(onfulfilled,onrejected){
+ onfulfilled = typeof onfulfilled === "function" ? onfulfilled : (val)=>val;
+ onrejected = typeof onrejected === "function" ? onrejected : (error)=>error;
}
catch
Promise.prototype.catch = function (errorCallback) {
return this.then(null, errorCallback);
};
resolve 和 reject
Promise.resolve = function (value) {
return new Promise((resolve) => resolve(value));
};
Promise.reject = function (value) {
return new Promise((resolve, reject) => reject(value));
};
Promise.all
all
方法接受一个数组,我们需要判断数组里面的Promise
方法,只有在then
方法执行后才算完成,可以定义一个完成变量保存完成的数量,当完成数量等于数组长度时,才resolve
最终的结果
Promise.all = function (values) {
return new Promise((resolve, reject) => {
let result = [];
// 执行完成的数量
let resultIndex = 0;
let len = values.length;
function processData(index, value) {
result[index] = value;
// 执行完成后加一
resultIndex++;
if (resultIndex === len) {
// 最终结果
resolve(result);
}
}
for (let i = 0; i < len; i++) {
let value = values[i];
let then = value.then;
if (then && typeof then === "function") {
then.call(
value,
(result) => {
processData(i, result);
},
(reject) => {
processData(i, reject);
}
);
} else {
processData(i, value);
}
}
});
};
race
race
执行返回最快完成的结果,所以当then
函数执行后直接执行resolve
返回结果:
Promise.race = function (values) {
return new Promise((resolve, reject) => {
for (let i = 0; i < values.length; i++) {
let value = values[i];
let then = value.then;
if (then && typeof then === "function") {
then.call(
value,
(result) => {
resolve(result);
},
reject
);
} else {
resolve(value);
break;
}
}
});
};
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。