启发自一月份的时候参加的某司的前端面试,当时我在和面试官聊项目经历的时候,谈到了Promise,引出了面试官此后长达半小时的对“如何用ES5的语法实现Promise”的灵魂拷问。因为答得不理想,所以今天就来补补课。
首先,来回顾一下Promise。Promise是ES6中提供的一个对象,能以同步、顺序的语句表达异步的操作。
(MDN开发者文档对于Promise的描述:Promise - JavaScript | MSN。)
下面是一个Promise的具体使用例子:
const fn = function(resolve, reject) {
console.log("Promise execution!");
let date = new Date();
let time = date.getTime();
if (time % 2 == 0) {
resolve("She loves me!");
} else {
reject("She doesn't love me!");
}
}
const defer = new Promise(fn);
defer.then(function(value) {
console.log("resolve: " + value);
}, function(reason) {
console.log("reject: " + reason);
});
上述代码中,Promise
对象的实例defer
接收了fn
函数作为参数。这里fn
函数被称为executor
函数,根据定义,“executor
函数会在Promise
构造函数返回新建的对象前被调用”。
但是,我们注意到fn
函数的定义中并没有对resolve
和reject
这两个函数进行定义。那么,在执行fn
函数语句的时候,这两个函数是如何得到执行的呢?
此外,当程序运行到defer.then
的时候,then
函数的两个参数都是回调函数 ——分别记做onfulfilled
和onrejected
—— 都与resolve
和reject
函数有着千丝万缕的关系:当fulfilled的时候,调用onfulfilled
函数,反之调用onrejected
函数。
(插一句,回调函数的记忆方式:callback: call then back. 调用之后返回主函数。)
为了实现以上功能,我采取的方法是利用setTimeout
函数,利用它的异步回调特性,来延迟resolve
和reject
!。
下面是我的实现:
function NewPromise(fn) {
this.value = null;
this.resolveFn;
this.rejectFn;
// execute the fn function.
// bind NewPromise object onto resolve and reject funtcions,
// so that the value of "this" keyword inside those two functions are both NewPromise object!
fn(this.resolve.bind(this), this.reject.bind(this));
}
NewPromise.prototype.resolve = function(value) {
this.value = value;
setTimeout(() => {
this.resolveFn(this.value);
}, 0);
}
NewPromise.prototype.reject = function(reason) {
this.value = reason;
setTimeout(() => {
this.rejectFn(this.value);
}, 0);
}
NewPromise.prototype.then = function(resolveFn, rejectFn) {
this.resolveFn = resolveFn;
this.rejectFn = rejectFn;
}
const fn = (resolve, reject) => {
console.log("Promise execution!");
let date = new Date();
let time = date.getTime();
if (time % 2 == 0) {
resolve("She loves me!");
} else {
reject("She doesn't love me!");
}
}
const defer = new NewPromise(fn);
defer.then((value) => {
console.log("resolve: " + value);
}, (reason) => {
console.log("reject: " + reason);
});
上述代码中,fn
函数首先在生成NewPromise
对象的时候得到执行。但是由于在调用resolve
和reject
的时候,使用了setTimeout
函数,虽然只是延迟0秒执行,但是由于JavaScript的单线程 + 事件循环的特性,实际resolveFn
或rejectFn
的调用必须等到主线程代码执行完毕才有机会开始。因此,主线程继续往下接着执行then
函数,在then
函数中,对NewPromise
对象中的resolveFn
和rejectFn
进行赋值。
(这里resolveFn
和rejectFn
只是得到了赋值,并没有执行!)
then
执行完毕后,主线程执行结束,再执行setTimeout
中的回调函数resolveFn
或者rejectFn
,此时,resolveFn
和rejectFn
已经被赋值,也顺利得到了value
和reason
的值。
以上就是我用ES5的语法写的Promise初级版本(虽然使用了一部分ES6的箭头函数语法…)。总结起来就是利用setTimeout
来延迟resolve
和reject
函数的后半部分执行,前半部分对value
赋值依旧正常执行,主线程结束后再利用resolve
回调resolveFn
,打印结果。(rejectFn
同理。)