Promise 原理探索与实现
Promise 简单介绍
其实大家都知道,在传统的异步编程中,如果异步之间存在依赖关系,比如需要一个异步操作执行成功之后再进行下一个一部操作,那就需要在第一个异步操作成功的回调函数中进行第二步异步操作,如果这样的依赖关系比较多的话,那就会造成很多的嵌套层数,从而使得代码的可读性和可维护性都变得很差,产生了所谓“回调地狱”,而 Promise 则是将回调嵌套改为链式调用,增加代码的可读性和可维护性。
Promise 原理解析
首先我们来看一下一个最简单但的 Promise 调用:
console.log("before promise");
const p1 = new Promise((resolve, reject) => {
console.log("promise start");
setTimeout(() => {
resolve("resolve test");
}, 0);
console.log("after setTimeout");
});
p1.then(
(res) => {
console.log("success");
console.log(res);
},
(err) => {
console.log("failed");
console.log(err);
}
);
console.log("after promise");
// before promise
// promise start
// after setTimeout
// after promise
// success
// resolve test
由运行结果可以看出,Promise 中的操作是立即执行的,一运行到 Promise 里面的内容就开始执行,所以打印了”promise start”,而其内部的异步操作被放入微/宏任务任务队列,等待执行,接着继续执行打印了”after setTimeout”,之后主线程继续执行,打印 after promise 。到此主线程执行完毕,开始检查微任务队列,then()函数被执行,收集 Promise 成功/失败的结果,并触发 resolve/reject,然后执行相应的回调函数,所以先打印”success”再打印 Promise 运行结果”resolve test”。
我们归纳一下,上述 Promise 的调用流程:
- Promise 的构造方法接受一个 executor(),在 new Promise()的时候就立刻执行这个 executor 回调;
- executor()内部的同步任务就立即执行了,而异步任务则是被放入微/宏任务事件队列,等待执行;
- 执行 then(),收集成功/失败的回调,放入成功/失败队列;
- 执行 executor()内部的异步任务,触发 resolve/reject,从成功/失败队列中取出回调依次执行;
仔细品一品上面的调用流程,我们会发现上述流程其实是一个观察者模式,在 Promise 中,代码执行顺序是: then 收集依赖-> 异步触发 resolve/reject-> 执行相应的回调。因此,我们来试着还原一下 Promise:
class MyPromise {
// 构造函数接受一个executor回调函数
constructor(executor) {
this._resolveQueue = []; // then收集的执行成功的回调队列
this._rejectQueue = []; // then收集的执行失败的回调队列
// 为什么使用箭头函数?因为resoleve/reject是在executor内部执行的,需要保证this指向不变,如果不使用
// 箭头函数,那就需要用一个额外的变量来记录this
let _resolve = (val) => {
// 从成功的回调队列中取出回调依次执行
while (this._resolveQueue.length) {
// 队列,队尾进入,队首出队
const callback = this._resolveQueue.shift();
callback(val);
}
};
// 不使用箭头函数的版本
// const _this = this;
// let _resolve = function (val) {
// // 从成功的回调队列中取出回调依次执行
// // console.log(this);
// while (_this._resolveQueue.length) {
// // 队列,队尾进入,队首出队
// const callback = _this._resolveQueue.shift();
// callback(val);
// }
// };
// reject逻辑与resolve逻辑相同
let _reject = (err) => {
while (this._rejectQueue.length) {
const callback = this._rejectQueue.shift();
callback(err);
}
};
// new Promise()时立刻执行executor,并传入resolve或者是reject
executor(_resolve, _reject);
}
// then方法,接受一个成功的回调和一个失败的回调,并push进相应的队列
then(resolveFn, rejectFn) {
this._resolveQueue.push(resolveFn);
this._rejectQueue.push(rejectFn);
}
}
测试一下:
//test MyPromise
const mypromise = new MyPromise((resolve, reject) => {
console.log("before setTimeout");
setTimeout(() => {
resolve("test mypromise");
// reject("promise failed");
});
});
mypromise.then(
(res) => {
console.log(res);
},
(err) => {
console.log(err);
}
);
// before setTimeout
// test mypromise
由结果可以看出,我的实现了 then 和 resolve/reject,我们能在回调函数中取得 promise 异步操作的返回值。这算是成功了第一步,接下来我们要一步步的完善 Promise。
Promise A+规范
如果只实现上述的 Promise 当然会是不行的,在 ES6 中对于 Pormise 定制了详细的规范,即Promises/A+规范。里面对实现 Promise 的一些术语、状态、解决流程都进行了详细的介绍,总结起来也就是下面几点:
- Promise 本质是一个状态机,且状态机的状态只有三种:Pending(等待态)、Fulfillied(执行态)、Rejected(拒绝态),状态的变更是单向的,只能从 Pending(等待态)->Fulfillied(执行态)或者是 Pending(等待态)->Rejected(拒绝态),且状态一旦改变就再也不能更改了;
- then 方法接收两个可选参数,分别对应状态改变时触发的回调。then 方法返回一个 promise。而且 then 方法可以被同一个 peomise 多次调用(链式调用);
Promise 的状态转换
接下来就是根据 Promises/A+规范规范来对我们的 Promise 的状态进行进一步的补充:
then 的链式调用
then 的链式调用应该算是 promise 的重难点了,也是 promise 设计比较巧妙的地方,我们先来看一下,promise 是怎么进行链式调用的:
const testP1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("testP1");
});
});
testP1
.then(
(res) => {
console.log(res);
// 可以返回一个promise实例
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("new promise");
});
});
},
(err) => {
console.log(err);
}
)
.then(
(res) => {
console.log(res);
// 可以直接返回一个值
return "new value";
},
(err) => {
console.log(err);
}
)
.then(
(res) => {
console.log(res);
},
(err) => {
console.log(err);
}
);
// testP1
// new promise
// new value
值得注意的是,当 promise 进行 then 函数链式调用时,第一次 then 函数中的回调会在当前事件循环内执行,而第一个 then 后面的 then 函数中得回调是否执行要看第一个 then 中是否有异步操作,如果有,则第二个 then 中的回调函数不一定在当前事件循环中执行,代码如下:
const testP1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("testP1");
});
});
const testP2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("testP2");
});
});
console.log("process start");
setTimeout(() => {
console.log("setTimeout");
});
testP1
.then(
(res) => {
console.log(res);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("new promise");
});
});
},
(err) => {
console.log(err);
}
)
.then(
(res) => {
console.log(res);
return "new value";
},
(err) => {
console.log(err);
}
)
.then(
(res) => {
console.log(res);
},
(err) => {
console.log(err);
}
);
testP2.then(
(res) => {
console.log(res);
},
() => {}
);
// process start
// testP1
// testP2
// setTimeout
// new promise
// new value
由上述例子可以看出,.then()回调是按顺序执行的,哪怕返回是 promise 或者是返回的是一个值,但他的执行顺序仍然是.then()调用的顺序。所以,实现链式调用有两个需要注意的地方:
- then()需要返回一个 Promise 这样才能找到下一个.then()方法,因此需要把 then()方法的返回值包装成 Promise;
- 考虑到 then()链式调用实行顺序的问题,我们需要对 then 的返回值进行讨论,确保 then 收集回调的顺序;
// 定义三种状态
const PENDING = "pending"; // 等待态
const FULIFILLED = "fulfilled"; // 执行态
const REJECTED = "rejected"; // 拒绝态
class MyPromise {
constructor(executor) {
// 增加一个属性记录promise状态
this.status = PENDING;
// 成功回调队列
this._resolveQueue = [];
// 失败执行回调
this._rejectQueue = [];
let _resolve = (val) => {
// 确保promised的状态只会从pending->fulfilled
if (this.status !== PENDING) return;
this.status = FULIFILLED;
// 使用队列数据结构可以存多个回调函数,实现then的多次调用,如果只是用一个变量存的话
// 那就算多次调用then也只会执行一次回调函数
while (this._resolveQueue.length) {
const callback = this._resolveQueue.shift();
callback(val);
}
};
let _reject = (err) => {
if (this.status !== PENDING) return;
this.status = REJECTED;
while (this._rejectQueue.length) {
const callback = this._rejectQueue.shift();
callback(err);
}
};
executor(_resolve, _reject);
}
then(resolveFn, rejectFn) {
// return 一个新的promise
return new Promise((resolve, reject) => {
// 对resolveFn进行包装,
const fulfilledFn = (value) => {
// 注意:这里需要catch错误
try {
// 对返回值进行讨论,如果返回的是promise,那么需要等当前promise转态变更,否则直接resolve
let tempResult = resolveFn(value);
tempResult instanceof MyPromise
? tempResult.then(resolve, reject)
: resolve(tempResult);
} catch (error) {
reject(error);
}
};
// 将后续then收集到的依赖都放入队列中保证了回调函数的顺序调用
this._resolveQueue.push(fulfilledFn);
const rejectedFn = (error) => {
try {
// 注意这里是rejectFn而不是rejectedFn
let tempResult = rejectFn(error);
tempResult instanceof MyPromise
? tempResult.then(resolve, reject)
: resolve(error);
} catch (error) {
reject(error);
}
};
this._rejectQueue.push(rejectedFn);
});
}
}
测试链式调用:
const testP1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("testP1");
});
});
testP1
.then(
(res) => {
console.log(res);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("new promise");
});
});
},
(err) => {
console.log(err);
}
)
.then(
(res) => {
console.log(res);
return "new value";
},
(err) => {
console.log(err);
}
)
.then(
(res) => {
console.log(res);
},
(err) => {
console.log(err);
}
);
// testP1
// new promise
// new value
由上述结果可以看出,我们实现的 MyPromise 的链式调用结果和 Promise 的调用结果一致。但是对于 then 方法我们还有两个细节需要处理:
- 值穿透:根据规范当 then()方法接收的参数不是 function 的时候,那这个参数应该被忽略。如果没有忽略,那将会抛出异常,导致链式调用中断;
- 状态为 resolve/reject 的情况:在实际情况中存在 resolve/reject 在 then()之前就被执行的情况,这种情况就不用再将回调函数放进回调队列,因为对于 fulfilled/rejected 状态的 promise 不会再执行回调,所以这个时候直接执行 then 回调函数即可;
如果对上述两种情况不太清楚的同学可以看下面的例子来加深理解:
const promiseP1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("promise P1");
});
});
const promiseP2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("promise P2");
});
});
// then传入参数情况
// 传入字符串或者是promise都不能打印出结果
promiseP1.then("test string", (err) => {
console.log(err);
});
promiseP1.then(promiseP2, (err) => {
console.log(err);
});
// 只有传function才可以
promiseP1.then(
(res) => {
console.log(res);
},
(err) => {
console.log(err);
}
);
// resolve/reject在then之前调用的情况
Promise.resolve(1).then(
(res) => {
console.log(res);
},
(err) => {
console.log(err);
}
);
// 1
// promise P1
继续完善我们的 MyPromise 代码,在 then 中收集回调函数之前对传入的参数进行判断处理,然后对回调函数进行包装,返回一个 promise 实例,并对当前 promise 的状态进行判断,如果是 reject/resolve,则直接执行 then 回调函数。另外,添加了静态的 resolve 和 reject 方法,使得用户可以直接调用 Promise.resolve(),如果用户传入的是一个 promise 则直接返回即可,如果不是则将其包装秤 promise 然后返回:
// 定义三种状态
const PENDING = "pending"; // 等待态
const FULIFILLED = "fulfilled"; // 执行态
const REJECTED = "rejected"; // 拒绝态
class MyPromise {
constructor(executor) {
// 增加一个属性记录promise状态
this.status = PENDING;
// 成功回调队列
this._resolveQueue = [];
// 失败执行回调
this._rejectQueue = [];
// 记录then()回调return的值
this._value = undefined;
let _resolve = (val) => {
// 确保promised的状态只会从pending->fulfilled
if (this.status !== PENDING) return;
this.status = FULIFILLED;
this._value = val;
// 使用队列数据结构可以存多个回调函数,实现then的多次调用,如果只是用一个变量存的话
// 那就算多次调用then也只会执行一次回调函数
while (this._resolveQueue.length) {
const callback = this._resolveQueue.shift();
callback(val);
}
};
let _reject = (err) => {
if (this.status !== PENDING) return;
this.status = REJECTED;
this._value = err;
while (this._rejectQueue.length) {
const callback = this._rejectQueue.shift();
callback(err);
}
};
executor(_resolve, _reject);
}
then(resolveFn, rejectFn) {
// 判断then接收的回调函数是否是function,如果不是那就忽略,让链式调用继续执行
typeof resolveFn !== "function"
? (resolveFn = (value) => {
value;
})
: null;
// 如果rejectFn不是函数,则抛出错误
typeof rejectFn !== "function"
? (reson) => {
throw new Error(reson instanceof Error ? reson.message : reson);
}
: null;
// return 一个新的promise
return new Promise((resolve, reject) => {
// 对resolveFn进行包装,
const fulfilledFn = (value) => {
// 注意:这里需要catch错误
try {
// 对返回值进行讨论,如果返回的是promise,那么需要等当前promise转态变更,否则直接resolve
let tempResult = resolveFn(value);
tempResult instanceof MyPromise
? tempResult.then(resolve, reject)
: resolve(tempResult);
} catch (error) {
reject(error);
}
};
// 将后续then收集到的依赖都放入队列中保证了回调函数的顺序调用
const rejectedFn = (error) => {
try {
// 注意这里是rejectFn而不是rejectedFn
let tempResult = rejectFn(error);
tempResult instanceof MyPromise
? tempResult.then(resolve, reject)
: resolve(error);
} catch (error) {
reject(error);
}
};
// 检查当前状态,如果是resolve/reject则直接执行回调
switch (this.status) {
// 如果是PENDING状态则将回调放入回调队列中
case PENDING:
this._resolveQueue.push(fulfilledFn);
this._rejectQueue.push(rejectedFn);
break;
// 如果已经是resolve则执行回调
// this._value是上一个then回调return的值
case FULIFILLED:
fulfilledFn(this._value);
break;
case REJECTED:
rejectedFn(this._value);
break;
}
});
}
// 添加静态resolve方法
static resolve(val) {
// 如果是Promise则直接return
if (val instanceof MyPromise) return val;
return new MyPromise((resolve) => resolve(val));
}
// 静态reject方法
static reject(reson) {
return new MyPromise((resolve, reject) => reject(reson));
}
}
//
测试一下我们的代码:
MyPromise.resolve(1).then((res) => {
console.log(res);
});
// 等价于
new MyPromise((resolve) => {
resolve(1);
}).then(
(res) => {
console.log(res);
},
(err) => {
console.log(err);
}
);
考虑同步任务的情况
我们之前实现的 Promise 只考虑了异步任务的情况,但是实际上 Promise 中的 executor 也可以是一个同步任务,且当 executor 是一个同步任务的时候,我们应该给 resolve/reject 的执行包一个 setTimeout,让其异步执行(实际上 Promise 实现时将 resolve/reject 放进了微任务队列,这里我们使用 setTimeout 是放进了宏任务队列),部分代码如下:
constructor(executor) {
// 增加一个属性记录promise状态
this.status = PENDING;
// 成功回调队列
this._resolveQueue = [];
// 失败执行回调
this._rejectQueue = [];
// 记录then()回调return的值
this._value = undefined;
let _resolve = (val) => {
const run = () => {
// 确保promised的状态只会从pending->fulfilled
if (this.status !== PENDING) return;
this.status = FULIFILLED;
this._value = val;
// 使用队列数据结构可以存多个回调函数,实现then的多次调用,如果只是用一个变量存的话
// 那就算多次调用then也只会执行一次回调函数
while (this._resolveQueue.length) {
const callback = this._resolveQueue.shift();
callback(val);
}
};
setTimeout(run);
};
let _reject = (err) => {
const run = () => {
if (this.status !== PENDING) return;
this.status = REJECTED;
this._value = err;
while (this._rejectQueue.length) {
const callback = this._rejectQueue.shift();
callback(err);
}
};
setTimeout(run);
};
executor(_resolve, _reject);
}
现在我们来测试一下之前所有的功能:
const p1 = new MyPromise((resolve, reject) => {
resolve(1); //同步测试
});
p1.then((res) => {
console.log(res);
return 2; //链式调用测试
})
.then() //值穿透测试
.then((res) => {
console.log(res);
return new MyPromise((resolve, reject) => {
resolve(3); //返回Promise测试
});
})
.then((res) => {
console.log(res);
throw new Error("reject测试"); //reject测试
})
.then(
() => {},
(err) => {
console.log(err);
}
);
// 1
// 2
// 3
// Error: reject测试
到这里我们的 Promise 就实现的差不多了,接下来我们一鼓作气将剩下的几个方法都加上:
Promise.protype.catch()
catch 方法返回的也是以一个 Promise,他的行为等价于 Promise.prototype.then(undefined, onRejected)。
catch(rejectFn) {
return this.then(undefined, rejectFn);
}
Promise.protype.all()
Promise.all 传入的是一个数组,需要在数组中所有的 promise 都完成后才执行回调,小编之前也实现过一次:实现 promiseAll
// 静态all方法
static all(promisesArr) {
let index = 0,
result = [];
return new MyPromise((resolve, reject) => {
promisesArr.map((promiseItem, i) => {
// MyPromise.resolve用于防止promiseItem不为promise的情况
MyPromise.resolve(promiseItem).then(
(res) => {
index++;
result[i](res);
if (index === promisesArr.length) {
resolve(result);
}
},
(err) => {
reject(err);
}
);
});
});
}
Promise.protype.race()
Promise.race 传的也是一个 promise 数组,它和 Promise.all 的区别在于他不用等所有的 promise 都执行完,它只要有一个 promise 执行完之后就可以执行回调函数了
// 静态race方法
static race(promisesArr) {
return new MyPromise((resolve, reject) => {
for (let promiseItem of promisesArr) {
MyPromise.resolve(promiseItem).then(
(value) => {
resolve(value);
},
(err) => {
reject(err);
}
);
}
});
}
MyPromise 完整代码
// 定义三种状态
const PENDING = "pending"; // 等待态
const FULIFILLED = "fulfilled"; // 执行态
const REJECTED = "rejected"; // 拒绝态
class MyPromise {
constructor(executor) {
// 增加一个属性记录promise状态
this.status = PENDING;
// 成功回调队列
this._resolveQueue = [];
// 失败执行回调
this._rejectQueue = [];
// 记录then()回调return的值
this._value = undefined;
let _resolve = (val) => {
const run = () => {
// 确保promised的状态只会从pending->fulfilled
if (this.status !== PENDING) return;
this.status = FULIFILLED;
this._value = val;
// 使用队列数据结构可以存多个回调函数,实现then的多次调用,如果只是用一个变量存的话
// 那就算多次调用then也只会执行一次回调函数
while (this._resolveQueue.length) {
const callback = this._resolveQueue.shift();
callback(val);
}
};
setTimeout(run);
};
let _reject = (err) => {
const run = () => {
if (this.status !== PENDING) return;
this.status = REJECTED;
this._value = err;
while (this._rejectQueue.length) {
const callback = this._rejectQueue.shift();
callback(err);
}
};
setTimeout(run);
};
executor(_resolve, _reject);
}
then(resolveFn, rejectFn) {
// 判断then接收的回调函数是否是function,如果不是那就忽略,让链式调用继续执行
typeof resolveFn !== "function"
? (resolveFn = (value) => {
value;
})
: null;
// 如果rejectFn不是函数,则抛出错误
typeof rejectFn !== "function"
? (reson) => {
throw new Error(reson instanceof Error ? reson.message : reson);
}
: null;
// return 一个新的promise
return new Promise((resolve, reject) => {
// 对resolveFn进行包装,
const fulfilledFn = (value) => {
// 注意:这里需要catch错误
try {
// 对返回值进行讨论,如果返回的是promise,那么需要等当前promise转态变更,否则直接resolve
let tempResult = resolveFn(value);
tempResult instanceof MyPromise
? tempResult.then(resolve, reject)
: resolve(tempResult);
} catch (error) {
reject(error);
}
};
// 将后续then收集到的依赖都放入队列中保证了回调函数的顺序调用
const rejectedFn = (error) => {
try {
// 注意这里是rejectFn而不是rejectedFn
let tempResult = rejectFn(error);
tempResult instanceof MyPromise
? tempResult.then(resolve, reject)
: resolve(error);
} catch (error) {
reject(error);
}
};
// 检查当前状态,如果是resolve/reject则直接执行回调
switch (this.status) {
// 如果是PENDING状态则将回调放入回调队列中
case PENDING:
this._resolveQueue.push(fulfilledFn);
this._rejectQueue.push(rejectedFn);
break;
// 如果已经是resolve则执行回调
// this._value是上一个then回调return的值
case FULIFILLED:
fulfilledFn(this._value);
break;
case REJECTED:
rejectedFn(this._value);
break;
}
});
}
// 添加静态resolve方法
static resolve(val) {
// 如果是Promise则直接return
if (val instanceof MyPromise) return val;
return new MyPromise((resolve) => resolve(val));
}
// 静态reject方法
static reject(reson) {
return new MyPromise((resolve, reject) => reject(reson));
}
catch(rejectFn) {
return this.then(undefined, rejectFn);
}
finally(callback) {
return this.then(
//MyPromise.resolve执行回调函数并将返回的值传递给then,也就是传递给下一个Promise
(value) => MyPromise.resolve(callback()).then(() => value),
(reason) =>
MyPromise.resolve(callback()).then(() => {
throw reason;
})
);
}
// 静态all方法
static all(promisesArr) {
let index = 0,
result = [];
return new MyPromise((resolve, reject) => {
promisesArr.map((promiseItem, i) => {
// MyPromise.resolve用于防止promiseItem不为promise的情况
MyPromise.resolve(promiseItem).then(
(res) => {
index++;
result[i](res);
if (index === promisesArr.length) {
resolve(result);
}
},
(err) => {
reject(err);
}
);
});
});
}
// 静态race方法
static race(promisesArr) {
return new MyPromise((resolve, reject) => {
for (let promiseItem of promisesArr) {
MyPromise.resolve(promiseItem).then(
(value) => {
resolve(value);
},
(err) => {
reject(err);
}
);
}
});
}
}
总结
这是笔者第一次实现一个 Promise,主要还是跟着别人的帖子进行仿写,其中学到了不少的知识点,也对 Promise 有了一个更深的理解,不过对于 finally 方法的实现,暂时还有一点疑虑,还需要再琢磨琢磨。路漫漫其修远兮,吾将上下而求索!