37行JS手撸Promise

160 阅读1分钟

背景

最近一段时间,准备换工作的事情,在B站上看了一些模拟面试视频,说实话,很多面试官问的问题,要我说我也不确定能否回答上来。针对一些常见的问题,比如生撸Promise,我也是刚刚花了一点时间,给出我的一个答案。

非常简单,算上测试代码+注释代码+换行,总共45行代码搞定。哪怕是初学者也能看明白:

实现

// 这是使用宿主环境原生Promise接口测测试用例
// function test(p) {
//     return new Promise((resolve, reject) => {
//         if (p == 1) resolve();
//         else reject();
//     })
// }

function MyPromise(fn) {
    this.resolveCallback = undefined;
    this.rejectCallback = undefined;
    fn(this.resolve.bind(this), this.reject.bind(this));
}

MyPromise.prototype.resolve = function() {
    this.resolveCallback(true);
}

MyPromise.prototype.reject = function() {
    this.rejectCallback(false);
}

MyPromise.prototype.then = function(callback) {
    this.resolveCallback = callback;
    return this;
}

MyPromise.prototype.catch = function(callback) {
    this.rejectCallback = callback;
    return this;
}

function api() {
    return new MyPromise(function(resolve, reject) {
        setTimeout(() => {
            if (Math.random() < 0.5) resolve();
            else reject();
        }, 1000);
    });
}

// 这是针对生撸的Promise写的一个测试用例
api().then(function(res) {
    console.log(4040, res)
}).catch(function(err) {
    console.log(4242, err)
})

注意点

  1. 针对xxx.then().catch()这种情况,此时不能执行业务代码,这里只是注册回调,并不能真正执行。
  2. resolve()和reject()直接调用时,此时this指向全局根作用域,因此需要通过bind来保存this作用域。

链式调用

此时,改变一下需求,需要链式调用,测试代码如下:

api()
    .then(function (res) {
        console.log(101, res);
        return api();
    })
    .then(function (res) {
        console.log(102, res);
        return api();
    })
    .then(function (res) {
        console.log(103, res);
        return api();
    })
    .then(function (res) {
        console.log(104, res);
        return api();
    })
    .then(function (res) {
        console.log(105, res);
    })
    .catch(function (err) {
        console.log(106, err);
    });

执行后,你会发现永远是最后一个then得到执行。原因是then多次调用之后this.resolveCallback值被覆盖了。因此,我们需要缓存每一个then里面的回调函数。在开发者调用resolve之后,遍历执行缓存的每一个函数,实现如下:

function MyPromise(fn) {
    this.resolveCallbacks = [];
    this.rejectCallbacks = [];
    this.resolveCallback = undefined;
    this.rejectCallback = undefined;
    fn(this.resolve.bind(this), this.reject.bind(this));
}

MyPromise.prototype.resolve = function (result) {
    this.resolveCallbacks.forEach(fn => {
        let ret = fn(result);
        if (ret instanceof MyPromise) {
            try {
                ret.resolve(result);
            } catch(e) {
                ret.reject(e);
            }
        }
    });
};

MyPromise.prototype.reject = function (result) {
    this.rejectCallbacks.forEach(fn => {
        fn(result);
    });
};

MyPromise.prototype.then = function (callback) {
    this.resolveCallbacks.push(callback);
    return this;
};

MyPromise.prototype.catch = function (callback) {
    this.rejectCallbacks.push(callback);
    return this;
};

(全文完)