@coconilu/mypromise v1.0.0
介绍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.pending
    this.fun = fun
    this.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) return
                this.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 value
    return 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 = 0
    return 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中给我留言。
谢谢你能看到这里。
7 years ago