【JS 深入】原生 Javascript 实现 ES6 中的 Promise 对象

有过 Ajax 经验的童鞋一定对“回调地狱”不陌生,为了取得异步请求的结果,我们往往要提供一个回调函数,一个异步请求的情况下并没有什么问题,如果在回调中我们还要进行异步请求,我们只能在回调中再嵌套回调函数,一旦异步次数多了,就变成了回调中嵌套回调、存在很多层结构的回调地狱,那样的代码根本不适合阅读。

ES6 中新增的 Promise 对象是一种新的异步解决方案,它的出现很好的解决了异步回调多层嵌套问题,通过 then 方法将以前的嵌套函数变成了平铺函数,代码一目了然,便于阅读。

但 ES6 的普及率仍然不高,在浏览器标准参差不齐的 Web 端,我们还是尽量少用吧,除非你不打算对低版本浏览器进行支持。

本着对 Promise 实现原理的研究,我们今天就来自己通过原生 JS 来实现一个 Promise 对象。

Promise 定义

所谓 Promise,就是一个单向状态机,里面保存着一个异步或同步动作,而这个状态机的状态会随着异步或同步动作的结果来决定。

Promise 有两个特点:

  1. 对象的状态不受外界影响。Promise 有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和 Rejected(已失败)。只有其内部保存的异步或同步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变 Promise 状态。
  2. 状态的单向改变。Promise 的状态只能从 Pedding 变成 Resolved / Rejected,无法从 Resolved / Rejected 再变成其他状态;同时任何时候都可以得到这个结果,就算改变已经发生了,你再对 Promise 添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

then方法

在 Promise 标准中,只规定了 Promise 的一个方法 then,其他关于 Promise 的实现提供的其他方法都是基于 then 方法上实现的,所以我们只要实现 then 这一个方法就行了。Promise 的 then 方法是用来注册在这个 Promise 状态确定后的回调,很明显,then 方法需要写在原型链上。现在所有 Promise 的实现在调用 then 方法后都会返回一个新的 Promise,关于这一点,Promise/A+ 标准并没有要求返回的这个Promise 是一个新的对象,但在 Promise/A 标准中,明确规定了 then 要返回一个新的对象,所以在我们的实现中,也让 then 返回一个新的Promise 对象。

构造函数

因为标准并没有指定如何构造一个 Promise 对象,所以我们用和 ES6 原生 Promise 一样的方式去构造 Promise:

function MyPromise(executor){
    this.state= "pedding";
    this.resolvedCallback = [];
    this.rejectedCallback = [];

    function resolve(value){
        if(this.state === "pedding"){
            this.state = "resolved";
            this.data = value;
            setTimeout(function(){
                this.resolvedCallback.forEach(function(fn){
                    fn && fn(value);
                })
            }.bind(this), 0);
        }
    }

    function reject(reason){
        if(this.state === "pedding"){
            this.state = "rejected";
            this.data = reason;
            setTimeout(function(){
                this.rejectedCallback.forEach(function(fn){
                    fn && fn(reason);
                })
            }.bind(this), 0);
        }
    }

    try{
        executor(resolve.bind(this), reject.bind(this));
    } catch(e){
        reject(e);
    }
}

上面的代码基本实现了 Promise 构造函数的主体,其中有两个注意点:

  1. 一定要给 executor 函数传两个参数:resolve 和 reject,因为 Promise 中的函数一定会接收到这两个函数。
  2. 因为 executor 也可能会出错(比如通过 throw 抛出错误),所以如果 executor 出错,这个 Promise 应该也要被 reject。

resolve 和 reject 的实现逻辑基本上就是在判断状态为 pending 之后把状态改为相应的值,并保存对应的 value 和 reason,之后执行相应的回调函数。 

then 的实现

then 方法的实现有点复杂,需要考虑的地方比较多,主要就是针对 Promise 当前的状态进行对应的处理,并且对 Promise 的结果的不同也要执行不同的操作。

MyPromise.prototype.then = function(resolvedCallback, rejectedCallback){
    var self = this;
    var promise;
    var x;
    
    // 设置默认操作
    resolvedCallback = typeof resolvedCallback === "function" ? resolvedCallback : function(value){return value;};
    rejectedCallback = typeof rejectedCallback === "function" ? rejectedCallback : function(reason){return reason;};
    
    // 当前状态为 pedding 时
    if(self.state === "pedding"){
    	// 主要逻辑就是将传入的回调压入 Promise 的回调栈中
    	return promise = new MyPromise(function(resolve, reject){
    		self.resolvedCallback.push(function(value){
    			try {
    				x = resolvedCallback(value);
    				// 调用特殊方法进行处理
    				resolvePromise(promise, x, resolve, reject);
    			} catch(e){
    				reject(e);
    			}
    		});
    
    		self.rejectedCallback.push(function(reason){
    			try {
    				x = rejectedCallback(reason);
    				// 调用特殊方法进行处理
    				resolvePromise(promise, x, resolve, reject);
    			} catch(e){
    				reject(e);
    			}
    		});
    	}) 
    }
    
    // 当前状态为 resolved 时
    if(self.state === "resolved"){
    	// 状态确定,直接执行对应的回调操作
    	return promise = new MyPromise(function(resolve, reject){
    		try{
    			x = resolvedCallback(self.data);
    			// 调用特殊方法进行处理
    			resolvePromise(promise, x, resolve, reject);
    		} catch(e){
    			reject(e);
    		}
    	})
    }
    
    // 当前状态为 rejected 时
    if(self.state === "rejected"){
    	// 逻辑同上
    	return promise = new MyPromise(function(resolve, reject){
    		try{
    			x = rejectedCallback(self.data);
    			// 调用特殊方法进行处理
    			resolvePromise(promise, x, resolve, reject);
    		} catch(e){
    			reject(e);
    		}
    	})
    }
    }
    
    function resolvePromise(promise, x, resolve, reject){
    // 当 x 是 MyPromise 或者其他 Promise 的实现时
    // 也就是说当 x 有 then 方法时,其就可能是一个 Promise 对象
    if (x != null && (typeof x === "object" || typeof x === "function")) {
    	try {
    		if (typeof x.then === "function") {
    			x.then(function(value){
    				resolvePromise(promise, value, resolve, reject);
    			}, reject)
    		} else {
    			resolve(x);
    		}
    	} catch(e) {
    		reject(e);
    	}
    } else {
    	resolve(x);
    }
}

测试效果

如何对 Promise 的实现进行测试,以判断其是否符合标准?这里有一个配套的测试脚本,只需要我们在一个 CommonJS 的模块中暴露一个 deferred 方法(即exports.deferred方法)。

function MyPromise(executor) {
    this.state = "pedding";
    this.resolvedCallback = [];
    this.rejectedCallback = [];

    function resolve(value) {
        if (this.state === "pedding") {
            this.state = "resolved";
            this.data = value;
            setTimeout(function () {
                this.resolvedCallback.forEach(function (fn) {
                    fn && fn(value);
                })
            }.bind(this), 0);
        }
    }

    function reject(reason) {
        if (this.state === "pedding") {
            this.state = "rejected";
            this.data = reason;
            setTimeout(function () {
                this.rejectedCallback.forEach(function (fn) {
                    fn && fn(reason);
                })
            }.bind(this), 0);
        }
    }

    try {
        executor(resolve.bind(this), reject.bind(this));
    } catch (e) {
        reject(e);
    }
}

MyPromise.prototype.then = function (resolvedCallback, rejectedCallback) {
    var self = this;
    var promise;
    var x;

    // 设置默认操作
    resolvedCallback = typeof resolvedCallback === "function" ? resolvedCallback : function (value) {
            return value;
        };
    rejectedCallback = typeof rejectedCallback === "function" ? rejectedCallback : function (reason) {
            return reason;
        };

    // 当前状态为 pedding 时
    if (self.state === "pedding") {
        // 主要逻辑就是将传入的回调压入 Promise 的回调栈中
        return promise = new MyPromise(function (resolve, reject) {
            self.resolvedCallback.push(function (value) {
                try {
                    x = resolvedCallback(value);
                    // 调用特殊方法进行处理
                    resolvePromise(promise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });

            self.rejectedCallback.push(function (reason) {
                try {
                    x = rejectedCallback(reason);
                    // 调用特殊方法进行处理
                    resolvePromise(promise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
        })
    }

    // 当前状态为 resolved 时
    if (self.state === "resolved") {
        // 状态确定,直接执行对应的回调操作
        return promise = new MyPromise(function (resolve, reject) {
            try {
                x = resolvedCallback(self.data);
                // 调用特殊方法进行处理
                resolvePromise(promise, x, resolve, reject);
            } catch (e) {
                reject(e);
            }
        })
    }

    // 当前状态为 rejected 时
    if (self.state === "rejected") {
        // 逻辑同上
        return promise = new MyPromise(function (resolve, reject) {
            try {
                x = rejectedCallback(self.data);
                // 调用特殊方法进行处理
                resolvePromise(promise, x, resolve, reject);
            } catch (e) {
                reject(e);
            }
        })
    }
};

function resolvePromise(promise, x, resolve, reject) {
    // 当 x 是 MyPromise 或者其他 Promise 的实现时
    // 也就是说当 x 有 then 方法时,其就可能是一个 Promise 对象
    if (x != null && (typeof x === "object" || typeof x === "function")) {
        try {
            if (typeof x.then === "function") {
                x.then(function (value) {
                    resolvePromise(promise, value, resolve, reject);
                }, reject)
            } else {
                resolve(x);
            }
        } catch (e) {
            reject(e);
        }
    } else {
        resolve(x);
    }
}

Promise.deferred = Promise.defer = function () {
    var  exp = {};
    exp.promise = new MyPromise(function (resolve, reject) {
        exp.resolve = resolve;
        exp.reject = reject;
    });
    return exp;
};

module.exports = Promise;

然后执行如下代码即可执行测试:

npm i -g promises-aplus-tests
promises-aplus-tests Promise.js

这里贴上我们的 Promise 实现的结果(仅供娱乐)

最后

写在最后的就是我们实际运用下看看效果。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>JS 实现 Promise</title>
    <meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1">
    <style>

    </style>
</head>
<body>
<script>
    function MyPromise(executor) {
        this.state = "pedding";
        this.resolvedCallback = [];
        this.rejectedCallback = [];

        function resolve(value) {
            if (this.state === "pedding") {
                this.state = "resolved";
                this.data = value;
                setTimeout(function () {
                    this.resolvedCallback.forEach(function (fn) {
                        fn && fn(value);
                    })
                }.bind(this), 0);
            }
        }

        function reject(reason) {
            if (this.state === "pedding") {
                this.state = "rejected";
                this.data = reason;
                setTimeout(function () {
                    this.rejectedCallback.forEach(function (fn) {
                        fn && fn(reason);
                    })
                }.bind(this), 0);
            }
        }

        try {
            executor(resolve.bind(this), reject.bind(this));
        } catch (e) {
            reject(e);
        }
    }

    MyPromise.prototype.then = function (resolvedCallback, rejectedCallback) {
        var self = this;
        var promise;
        var x;

        // 设置默认操作
        resolvedCallback = typeof resolvedCallback === "function" ? resolvedCallback : function (value) {
                return value;
            };
        rejectedCallback = typeof rejectedCallback === "function" ? rejectedCallback : function (reason) {
                return reason;
            };

        // 当前状态为 pedding 时
        if (self.state === "pedding") {
            // 主要逻辑就是将传入的回调压入 Promise 的回调栈中
            return promise = new MyPromise(function (resolve, reject) {
                self.resolvedCallback.push(function (value) {
                    try {
                        x = resolvedCallback(value);
                        // 调用特殊方法进行处理
                        resolvePromise(promise, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });

                self.rejectedCallback.push(function (reason) {
                    try {
                        x = rejectedCallback(reason);
                        // 调用特殊方法进行处理
                        resolvePromise(promise, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
            })
        }

        // 当前状态为 resolved 时
        if (self.state === "resolved") {
            // 状态确定,直接执行对应的回调操作
            return promise = new MyPromise(function (resolve, reject) {
                try {
                    x = resolvedCallback(self.data);
                    // 调用特殊方法进行处理
                    resolvePromise(promise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            })
        }

        // 当前状态为 rejected 时
        if (self.state === "rejected") {
            // 逻辑同上
            return promise = new MyPromise(function (resolve, reject) {
                try {
                    x = rejectedCallback(self.data);
                    // 调用特殊方法进行处理
                    resolvePromise(promise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            })
        }
    };

    MyPromise.prototype.catch = function(rejectedCallback) {
    	this.then(null, rejectedCallback);
    }

    MyPromise.resolve = function(x) {
    	if (typeof x === "object") {
    		if (x instanceof MyPromise) {
    			return x;
    		}

    		if (typeof x.then === "function") {
    			return new MyPromise((resolve, reject) => {
    				x.then(resolve, reject)
    			});
            }
        }
        return new MyPromise((resolve, reject) => {
        	resolve();
        })
    }

    function resolvePromise(promise, x, resolve, reject) {
        // 当 x 是 MyPromise 或者其他 Promise 的实现时
        // 也就是说当 x 有 then 方法时,其就可能是一个 Promise 对象
        if (x != null && (typeof x === "object" || typeof x === "function")) {
            try {
                if (typeof x.then === "function") {
                    x.then(function (value) {
                        resolvePromise(promise, value, resolve, reject);
                    }, reject)
                } else {
                    resolve(x);
                }
            } catch (e) {
                reject(e);
            }
        } else {
            resolve(x);
        }
    }
</script>
<script>
	var time = Date.now()
    var promise = new MyPromise((resolve, reject) => {
        setTimeout(() => {
            resolve("First");
        }, 1000)
    });

    promise.then((value) => {
    	console.log(value);
    	console.log(Date.now() - time);
    })

    promise.then((value) => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve("Second");
            }, 1000)
        })
    }).then((value) => {
        console.log(value);
    	console.log(Date.now() - time);
    }, (err) => {
        console.log(err);
    	console.log(Date.now() - time);
    })

    promise.then((value) => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                reject("Third");
            }, 2000)
        })
    }).then((value) => {
        console.log(value);
    	console.log(Date.now() - time);
    }, (err) => {
        console.log(err);
    	console.log(Date.now() - time);
    	throw Error("Error");
    }).catch((err) => {
    	console.log(err.toString());
    	console.log(Date.now() - time);
    })


    var p1 = MyPromise.resolve("Hello");
    p1.then(() => {
    	console.log("new Promise p1");
    }, () => {
    	console.log("new Promise p1");
    })

    var p1 = MyPromise.resolve({
    	then: (resolve, reject) => {
    		setTimeout(() => {
    			resolve("new Promise p2");
    		}, 1000)
    	}
    });
    p1.then((value) => {
    	console.log(value);
    }, (err) => {
    	console.log(err);
    })

</script>
</body>
</html>