介绍MyPromise
MyPromise是模仿Promise实现的一套异步处理框架,但是MyPromise是使用setTimeout和ES6语法实现的。其中ES6语法是可选的。
它跟ES6标准实现的Promise是有区别的:
ES6的Promise是使用micro-task-queue,回调在一轮事件循环里就可以被主线程执行;
而MyPromise由于是使用setTimeout实现的,回调是放在macro-task-queue里,所以会在下一轮事件循环被主线程执行。
micro-task-queue和macro-task-queue的介绍可以看我的另一篇文章
以ES6的Promise为参考
ES6的Promise规范可以参考MDN。
MyPromise将依次实现以下API:
Promise.prototype.then()
Promise.prototype.catch()
Promise.prototype.finally()
Promise.resolve()
Promise.reject()
Promise.race()
Promise.all()
复制代码
必备的知识
- ES6的一些基础语法,如class
Function.prototype.bind()
- 闭包
- setTimeout异步处理思维
MyPromise原理
- 基于setTimeout实现的异步框架;
- 存储每一次链式调用的回调;
- 在发起resolve(成功)或reject(失败)时发起异步处理。
用一张图描述整个业务逻辑:
逐个实现API
完整的代码可以看:./src/MyPromise.js
1. MyPromise的构造函数
首先需要保留MyPromise的状态,有3个状态:pending(未完成状态)、resolved(成功状态)、rejected(失败状态);
其次需要存储链式调用中的回调函数(下面称之为chain-callback
),使用数组来存储它们;
最后调用构造函数传入进来的实参回调(下面称之为argument-callback
)。
代码如下:
constructor(fun) {this.state = stateEnum.pendingthis.fun = funthis.onResolvedArray = [];this.onRejectedArray = [];try {this.fun.call(undefined, resolve.bind(this), reject.bind(this))} catch (err) {// 处理错误reject.bind(this)(err)}
}
复制代码
2. 对于resolve和reject函数
这两个函数才是MyPromise的灵魂所在,我并没有把它们放在构造函数或者构造函数的prototype里是因为不想它被使用者破坏。 这里使用resolve和reject命名是为了跟随潮流,别的命名也是可以的。 先把代码贴出来,由于resolve和reject函数逻辑差不多,这里仅讲解一下resolve:
const resolve = function (value) {if (this.state === stateEnum.pending) {setTimeout(() => {let result;// 过滤onResolvedArray中undefined的情况,注意避免死循环while (!(this.onResolvedArray[0] instanceof Function)) {if (!this.onResolvedArray.length) returnthis.onResolvedArray.shift()this.onRejectedArray.shift()}try {result = this.onResolvedArray[0].call(undefined, value)// 判断返回的是不是一个Promise对象if (!(result instanceof MyPromise)) {result = MyPromise.resolve()}this.state = stateEnum.resolved} catch (err) {result = MyPromise.reject(err)} finally {this.onResolvedArray.shift()this.onRejectedArray.shift()result.fillCallbacks(this.onResolvedArray, this.onRejectedArray)}})}
}
复制代码
- 为了防止在
argument-callback
多次调用resolve引起的不正常现象,我们对MyPromise对象的状态进行判断; - 当
argument-callback
里调用了resolve之后,除了进行状态判断,还会向定时器线程发起一个异步调用(也就是setTimeout),让它立即把回调放入任务队列(macro-task-queue)中,等待主线程的执行; - 等到主线程执行这个回调的时候,会把
chain-callback
中取出一个回调执行,并判断返回的结果是不是另一个MyPromise,如果不是则创造一个没有带值的MyPromise,并把chain-callback
的剩下回调传给新的MyPromise; - 继续等待MyPromise里的
argument-callback
调用resolve方法,回到2。
这就是链式调用的关键逻辑。
3. then、catch、finally
这三个函数都是为了填充MyPromise对象的chain-callback
,逻辑很简单。
then() {this.onResolvedArray.push(arguments[0])this.onRejectedArray.push(arguments[1])return this
}
catch () {this.onResolvedArray.push(undefined)this.onRejectedArray.push(arguments[0])return this
}
finally() {this.onResolvedArray.push(arguments[0])this.onRejectedArray.push(arguments[0])return this
}
复制代码
4. MyPromise.resolve和MyPromise.reject
这两个方法不同于前面介绍的resolve和reject,它们是MyPromise类上的方法,主要作用是创建一个MyPromise对象并调用argument-callback
里的resolve或reject:
static resolve(value) {if (value instanceof MyPromise) return valuereturn new MyPromise((_resolve, _reject) => {_resolve(value)})
}static reject(err) {return new MyPromise((_resolve, _reject) => {_reject(err)})
}
复制代码
5. MyPromise.race和MyPromise.all
到这里,其实已经介绍完MyPromise的主要代码和逻辑。
而MyPromise.race和MyPromise.all提供了一些特殊的用途,借用MDN的介绍。
Promise.race(iterable) 方法返回一个 promise ,并伴随着 promise对象解决的返回值或拒绝的错误原因, 只要 iterable 中有一个 promise 对象"解决(resolve)"或"拒绝(reject)"。
static race(arr) {const paramsArr = Array.prototype.slice.call(arr)return new MyPromise((resolve, reject) => {paramsArr.map((item, index, arr) => {if (!(item instanceof MyPromise)) {item = MyPromise.resolve(item)}item.then(val => {resolve(val)}, err => {reject(err)})})})
}
复制代码
Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败(rejected),此实例回调失败(rejecte),失败原因的是第一个失败 promise 的结果。
static all(arr) {const results = []const paramsArr = Array.prototype.slice.call(arr)let numOfPromise = 0return new MyPromise((resolve, reject) => {paramsArr.map((value, index, arr) => {if (value instanceof MyPromise) {++numOfPromise;value.then(val => {results[index] = val;--numOfPromise;if (numOfPromise === 0) {resolve(results)}}, err => {reject(err)})} else {results[index] = value}})})
}
复制代码
测试
上面主要介绍了resolve,而reject只是只言片语带过。希望源码和测试代码能解答你心中的疑惑。
测试代码使用了
Jest
框架
写在最后
如果文章哪里写的不对,还望不吝指教。
若有疑问,可以在issue中给我留言。
谢谢你能看到这里。