Promise的深入:实现Promise的核心功能

Promise的深入:实现Promise的核心功能

前言

Promise是js中的一个重要模块,它可以方便我们更优雅地管理异步回调问题,彻底解决了回调地狱问题,并且其核心代码很短。了解Promise的运作原理和机制,会有莫大的好处,就算转变了编程语言,只要还有异步这个概念,就可以自己动手封装。

最简易版

Promise核心功能之一就是管理回调任务,在异步任务结束后,通过调用resolve方法,实现调用回调并且传递参数:

class MyPromise {
    constructor(fn) {
        this.callback = null;
        fn(this.resolve.bind(this));
    }

    resolve(arg) {
		this.callback(arg);
    }
    
    done(handle) {
        this.callback = handle;
    }
}

这算是最最最简易的Promise了,运行一下看结果:

new MyPromise((res) => {
    setTimeout(() => {
        res("1秒过去了");
    }, 1000);
}).done((value) => {
    console.log(value);
})

结果:
1秒过去了

运行结果没有问题,一秒后输出了res中的参数,但是这样的Promise是根本不够用的。

引入状态

思考这样一个问题:
如果童鞋们对Promise使用很多的的话,就可能会发现这样的现象:
现在new了一个Promise对象,丢给了它一个异步任务,然后我去楼下吃了顿饭,等到我回来时,异步任务早已完成,这时我再调用then方法,它仍然能够给出结果。
可见上述的简易版并不具有这样的功能,于是对它进行改造:

class MyPromise {
    constructor(fn) {
    	this.state = "PENDING"; //添加状态
    	this.value = null; //保存结果
        this.callback = null;
        fn(this.resolve.bind(this));
    }

    resolve(arg) {
    	this.state = "RESOLVED";
    	this.value = arg; //保存结果
		this.handle(this.callback);
    }
    
    done(callback) {
        this.handle(callback);
    }
    
    handle(callback) {
    	if(this.state === "PENDING") {
    		this.callback = callback;
    		return;
    	}
    	callback(this.value);
    }
}

上述代码增加了状态和保存了结果,如果resolve被调用,就改变状态并保存结果,在调用done方法时,如果已经是RESOLVED状态,则直接调用回调:

let test = new MyPromise((res) => {
    setTimeout(() => {
        res("都第3秒了我才输出");
    }, 1000);
});

setTimeout((() => {
    console.log("2秒过去了");
}), 2000);

setTimeout(() => {
    test.done((value) => {
        console.log(value);
    });
}, 3000);

结果:
2秒过去了
都第3秒了我才输出

上述代码解决了之前提到的问题,但是到现在为止,Promise最大的魅力还没有体现出来。

最简单的链式调用

要实现Promise的链式调用,就必须搞清楚链式调用的本质,先拿我们常用的Promise举例,如果童鞋们的学习没有偷懒的话,应该可以知道then方法发挥的是一个Promise对象,同时回调的函数不止一个,所以可以这样修改代码:

class MyPromise {
    constructor(fn) {
    	this.state = "PENDING";
    	this.value = null;
        this.callbacks = []; //改为数组
        fn(this.resolve.bind(this));
    }

    resolve(arg) {
    	this.state = "RESOLVED";
    	this.value = arg;
        this.callbacks.forEach(callback => {
            this.handle(callback);  //迭代调用队列中所有的回调
        })
    }
    
    done(callback) {
        this.handle(callback);
        return this; //返回本身
    }
    
    handle(callback) {
    	if(this.state === "PENDING") {
    		this.callbacks.push(callback); //将回调压入队列
    		return;
    	}
    	if(callback) {
            callback(this.value);
        }
    }
}

上述代码在done方法中返回了this,即Promise对象本身,现在我们来试一下结果吧!

new MyPromise((res) => {
    setTimeout(() => {
        console.log(1);
        res()
    }, 1000)
}).done(() => {
    console.log(2);
}).done(() => {
    console.log(3);
})

结果:
1
2
3

上述代码实现了最低限度的链式调用但是距离我们想要的还差很远。

链式调用之间传递数据

设想这样的一个场景:

new MyPromise((res) => {
    setTimeout(() => {
        res(1)
    }, 1000)
}).done((data) => {
    console.log(data);
    return 2;
}).done((data) => {
    console.log(data);
    return 3;
}).done((data) => {
    console.log(data);
})

这个例子中,每次链式调用都会返回不同的结果,然而之前的代码并不能实现这种功能,所以说每次done方法都必须返回一个新的Promise对象,而不是他自己,并且这个新Promise对象要隐式地被RESOLVED

class MyPromise {
    constructor(fn) {
    	this.state = "PENDING";
    	this.value = null;
        this.callbacks = [];
        fn(this.resolve.bind(this));
    }

    resolve(arg) {
    	this.state = "RESOLVED";
    	this.value = arg;
        this.callbacks.forEach(callback => {
            this.handle(callback);
        })
    }
    
    done(callback) {
        return new MyPromise((res) => {  //返回一个新的MyPormise
            this.handle({
                onfinished: callback,
                resolve: res,  //将返回的MyPromise的resolve方法传递给handle方法
            })
        });
    }

    handle(callback) {
    	if(this.state === "PENDING") {
    		this.callbacks.push(callback);
    		return;
    	}
    	if(callback) {
            let r = callback.onfinished(this.value); //获得回调的返回值
            callback.resolve(r);  //通过resolve传递给下一个done方法
        }
    }
}

着重分析一下改动的部分:
首先在done方法里返回一个新的Promise实例,为了能够操控这个新的实例,将它的resolve方法一并保存,然后在handle里通过调用它来将callback的返回值作为参数传递给下一条链子:

new MyPromise((res) => {
    setTimeout(() => {
        res(1)
    }, 1000)
}).done((data) => {
    console.log(data);
    return data + 1;
}).done((data) => {
    console.log(data);
    return data + 1;
}).done((data) => {
    console.log(data);
})

结果:
1
2
3

准备好了吗,接下来就要实现最终奥义:异步任务的链式调用。

最终Boss

通过现有的代码,我们可以发现,如果返回值是一个MyPromise那么它将会被传至下一个链子的参数中,通过这个现象,向上溯源,我们可以在上层调用的resolve中拿到这个值加以判断。其次就是如何把这个MyPromise中的reslove的参数传递下去和如何让新的异步任务完成后再进行下一步了。
先看实现:

class MyPromise {
    constructor(fn) {
    	this.state = "PENDING";
    	this.value = null;
        this.key = "Promise"; //便于判断师是否为Promise对象
        this.callbacks = [];
        fn(this.resolve.bind(this));
    }

    resolve(arg) {
        if(typeof arg === "object" && arg.key === "Promise") { //判断
            let done = arg.done;  //获得回调中返回的Mypromise实例的done方法
            done.call(arg, this.resolve.bind(this));  //手动调用done方法,并将当前Promise的resolve方法作为callback传递
            return;  //中止
        }
    	this.state = "RESOLVED";
    	this.value = arg;
        this.callbacks.forEach(callback => {
            this.handle(callback);
        })
    }
    
    done(callback) {
        return new MyPromise((res) => {
            this.handle({
                onfinished: callback,
                resolve: res, 
            })
        });
    }

    handle(callback) {
    	if(this.state === "PENDING") {
    		this.callbacks.push(callback);
    		return;
    	}
    	if(callback) {
            let r = callback.onfinished(this.value);
            callback.resolve(r);
        }
    }
}

这次改动比较难以理解,首先要获得返回的新实例的done方法,然后手动调用它,将当前Promise的reslove方法传入作为新实例的回调,当新实例RESOLVED后,新实例就会调用它的回调,也就是外层Promise的reslove方法,并将结果作为参数传入,这样就实现了外部Promise的解决依赖于内部Promise的解决,并且可以正常传递参数:

new MyPromise((res) => {
    setTimeout(() => {
        res("1秒过去了")
    }, 1000)
}).done((data) => {
    console.log(data);
    return new MyPromise((res) => {
        setTimeout(() => {
            res("2秒过去了");
        }, 1000);
    });
}).done((data) => {
    console.log(data);
    return new MyPromise((res) => {
        setTimeout(() => {
            res("3秒过去了");
        }, 1000);
    });
}).done((data) => {
    console.log(data);
});
结果:
1秒过去了
2秒过去了
3秒过去了

Promise可以说是js中很经典的一个面试题了,同时它的难度还是有的,弄懂他不仅能提高使用js的技巧性,还能由小小的成就感o( ̄▽ ̄)ブ。

你可能感兴趣的:(javascript,node.js,es6)