JS案例:实现Promise

本文最后更新于:9 个月前

说到ES6的Promise,大家并不陌生,它是JS中解决异步的方法之一
其优点:避免回调地狱,链式调用,函数思路清晰,逻辑相对回调函数和事件发布/订阅更强
缺点:理解性差,异步操作在promise构造函数内部

这段时间在整理面试题时发现,Promise的实现可以单独拿出来分享,于是自己简单实现了一下
码云地址:https://gitee.com/DieHunter/myCode/tree/master/Promise%E5%B0%81%E8%A3%85

实现完整功能之前,我们先了解一下Promise的用法,并实现一个简单的Promise和Promise.then函数

Promise(executor: (resolve: (value?: any) => void, reject: (reason?: any) => void) => void): Promise<any>

上述配置提示中显示,Promise需要传入一个回调函数,函数有两个参数(resolve, reject),第一个是异步执行成功后回调,另一个是失败时的回调。Promise.then方法是执行异步函数成功,即resolve执行时,才会执行then方法中的回调,以下是Promise最简单的一个用法

new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve('success')// 传参
    }, 500)
}).then(function (res) {
    console.log(res) // success
})

下面,我们实现一个最简单的Promise,用于解析Promise.then的原理,主要原理就是用两个回调函数嵌套,将函数作为参数放入至异步操作中,当异步操作执行后再执行作为参数的回调

function MyPromise(fn) { // 主要原理就是用两个回调函数嵌套,将函数作为参数放入至异步操作中,当异步操作执行后再执行作为参数的回调
    var _this = this;
    _this.params = null; // 传递的参数
    _this.tempResolve = null // _this.tempResolve的作用是将参数传递至then方法中
    function resolve(params) { // 异步操作之后才会执行该方法,执行前一直等待
        _this.params = params
        _this.tempResolve(_this.params)
    }
    fn(resolve) // 将resolve通过回调返回到异步操作函数中,当resolve执行时,才是异步操作执行后
}

MyPromise.prototype.then = function (_resolve) { // 异步操作传递参数,简言之就是连接then和resolve
    var _this = this
    _this.tempResolve = function () {
        _resolve(_this.params)
    }
}
MyPromise.prototype.constructor = MyPromise

new MyPromise(function (res, rej) {
        setTimeout(function () {
            res('success')
        }, 1000)
    })
    .then(function (res) {
        console.log(res) // success
    })

如果理解了上面的代码,就已经成功了一半,接下来,我们对Promise进行深入的实现,与上述代码差别是,添加then的链式调用,其实可以理解为多层Promise嵌套,但是我们需要对每层Promise做出操作,所以,我们在每层promise中添加status用于记录当前promise是否已执行,tempResolve也要改成tempResolveList,因为需要执行的函数不止一个,变成了一个队列,在上面代码的基础上,我们对resolve进行优化

function resolve(params) { // 异步操作之后才会执行该方法,执行前一直等待
    if (_this.status === 'pending') {
        _this.status = 'resolve'; // 进入函数后,立即修改函数状态,防止下面的循环重复执行函数
        _this.params = params;
        for (var i = 0; i < array.length; i++) {
            _this.tempResolveList[i](_this.params) // 执行所有then的链式调用的函数
        }
    }
}

除此之外,在then函数中,还需要添加一段代码,其目的是将Promise返回到下一层链式调用,将回调函数通过resolve传递至下一层,达到依次同步执行的目的

MyPromise.prototype.then = function (tempResolve) { // 异步操作传递参数,简言之就是连接then和resolve
    var _this = this
    var _promise = new MyPromise(function (resolve, reject) {
        if (_this.status == 'pending') {
            _this.tempResolveList.push(function () {
                resolve(tempResolve(_this
                    .params)) // 将上一层tempResolve通过resolve的参数异步传递给下一层的Promise中,每层都会异步叠加
            })
        }
    })
    return _promise // 返回Promise用于链式调用
}

完成之后,我们会发现一个问题,当我们通过resolve传递tempResolve执行结果时,只有一层链式调用的话,返回的是原回调函数,当到了第二层时,返回的是上一层的resolve,此时我们需要在resolve函数之前做个过滤,并且把参数中的then放在本层,直接执行

if (params && typeof params === 'function' || typeof params ===
    'object') { // 这里要判断参数是普通参数params,还是MyPromise方法,链式调用一定会产生MyPromise构造函数
    var _then = params.then // 如果参数是MyPromise构造函数,则将上层的then放到本层继续执行后续操作
    if (typeof _then === 'function') {
        _then.call(params, resolve); // 链式调用then
        return;
    }
}

Promise.then的链式调用完整代码

function MyPromise(fn) { // 主要原理就是用两个回调函数嵌套,将函数作为参数放入至异步操作中,当异步操作执行后再执行作为参数的回调
    var _this = this;
    _this.status = 'pending'; // 每层Promise的待定状态,只有当前Promise处于pending的时候,才会执行异步函数
    _this.params = null; // 传递的参数
    _this.tempResolveList = new Array() // 储存链式调用then中的函数队列
    function resolve(params) { // 异步操作之后才会执行该方法,执行前一直等待
        if (params && typeof params === 'function' || typeof params ===
            'object') { // 这里要判断参数是普通参数params,还是MyPromise方法,链式调用一定会产生MyPromise构造函数
            var _then = params.then // 如果参数是MyPromise构造函数,则将上层的then放到本层继续执行后续操作
            if (typeof _then === 'function') {
                _then.call(params, resolve); // 链式调用then
                return;
            }
        }
        if (_this.status === 'pending') {
            _this.status = 'resolve'; // 进入函数后,立即修改函数状态,防止下面的循环重复执行函数
            _this.params = params;
            for (var i = 0; i < _this.tempResolveList.length; i++) {
                _this.tempResolveList[i](_this.params) // 执行所有then的链式调用的函数
            }
        }
    }
    fn(resolve) // 将resolve通过回调返回到异步操作函数中,当resolve执行时,才是异步操作执行后
}

MyPromise.prototype.then = function (tempResolve) { // 异步操作传递参数,简言之就是连接then和resolve
    var _this = this
    var _promise = new MyPromise(function (resolve, reject) {
        if (_this.status == 'pending') {
            _this.tempResolveList.push(function () {
                resolve(tempResolve(_this
                    .params)) // 将上一层tempResolve通过参数异步传递给下一层的Promise中,每层都会异步叠加
            })
        }
    })
    return _promise // 返回Promise用于链式调用
}
MyPromise.prototype.constructor = MyPromise
var count = 1
new MyPromise(function (res, rej) {
        setTimeout(function () {
            res('success' + count++)
        }, 1000)
    })
    .then(function (res) {
        console.log(res) // success1
        return new MyPromise(function (res, rej) {
            setTimeout(function () {
                res('success' + count++)
            }, 1000)
        })
    }).then(function (res) {
        console.log(res) // success2
        return new MyPromise(function (res, rej) {
            setTimeout(function () {
                res('success' + count++)
            }, 1000)
        })
    }).then(function (res) {
        console.log(res) // success3
    })

实现了链式调用后,我们对reject以及catch进行一个简单的实现,其实现过程与then相似,我们对一些方法封装一下,得到以下代码(catch没有完善链式调用,导致then方法执行数量大于1时失效)

function MyPromise(fn) { // 主要原理就是用两个回调函数嵌套,将函数作为参数放入至异步操作中,当异步操作执行后再执行作为参数的回调
    var _this = this;
    _this.status = 'pending'; // 每层Promise的待定状态,只有当前Promise处于pending的时候,才会执行异步函数
    _this.params = null; // 传递的参数
    _this.tempResolveList = new Array() // 储存链式调用then中的函数队列
    _this.tempRejectList = new Array() // 储存链式调用catch中的函数队列

    _this.runCommandList = function (_status, _params,
        _commandList) { // 若函数状态是pending待定状态,函数执行后会有两个状态,resolve和reject
        if (_params && typeof _params === 'function' || typeof _params ===
            'object') { // 这里要判断参数是普通参数params,还是MyPromise方法,链式调用一定会产生MyPromise构造函数
            var _then = _params.then // 如果参数是MyPromise构造函数,则将上层的then放到本层继续执行后续操作
            if (typeof _then === 'function') {
                _then.call(_params, resolve); // 链式调用then
                return;
            }
        }
        if (_this.status === 'pending') {
            _this.status = _status; // 进入函数后,立即修改函数状态,防止下面的循环重复执行函数
            _this.params = _params;
            for (var i = 0; i < _commandList.length; i++) {
                _commandList[i](_this.params) // 执行所有then的链式调用的函数
            }
        }
    }
    _this.runCallBack = function (resolve, reject, finishFn) {
        return function () {
            try {
                var temp = finishFn(_this.params);
                resolve(temp);
            } catch (error) {
                reject(error);
            }
        }
    }
    _this.createPromise = function (temp, tempList) {
        var _this = this
        return new MyPromise(function (resolve, reject) {
            if (_this.status == 'pending') {
                tempList.push(_this.runCallBack(resolve, reject,
                    temp)) // 将上一层tempResolve通过参数异步传递给下一层的Promise中,每层都会异步叠加
            }
        })
    }

    function resolve(params) { // 异步操作之后才会执行该方法,执行前一直等待,通过回调返回到new Promise(fn)参数中
        _this.runCommandList('resolve', params, _this.tempResolveList)
    }

    function reject(params) { // 异步操作之后才会执行该方法,执行前一直等待,通过回调返回到new Promise(fn)参数中
        _this.runCommandList('reject', params, _this.tempRejectList)
    }
    try { //捕获异常
        fn(resolve, reject)
    } catch (error) {
        reject(error)
    } // 将resolve通过回调返回到异步操作函数中,当resolve执行时,才是异步操作执行后
}

MyPromise.prototype.then = function (tempResolve) { // 异步操作传递参数,简言之就是连接then和resolve
    var _this = this
    var _promise = _this.createPromise(tempResolve, _this.tempResolveList)
    _promise.catch = function (tempReject) { // 异步操作传递参数,简言之就是连接then和resolve
        _this.createPromise(tempReject, _this.tempRejectList)
    }
    return _promise // 返回Promise用于链式调用
}



MyPromise.prototype.constructor = MyPromise
var count = 1
new MyPromise(function (res, rej) {
        setTimeout(function () {
            rej('success' + count++)
        }, 1000)
        // setTimeout(function () {
        //     res('success' + count++)
        // }, 1000)
    })
    .then(function (res) {
        console.log(res) // success1
        return new MyPromise(function (res, rej) {
            setTimeout(function () {
                res('success' + count++)
            }, 1000)
        })
    }).catch(function (err) {
        console.log(err) // success1
    })

总结:代码可能有地方不完善,欢迎大佬指出