最近看了一篇关于Promise
内部实现原理的文章Javascript in wicked detail。作者从简明的例子入手,一步一步的构建健壮的Promise
实现。我就拿作者文中的代码实例梳理下文章的核心内容。
大家一定看到过嵌套很深回调函数,那么如何在保证代码流程才能将这些纵向嵌套的代码变成横向偏平的呢?
doSomething(function(value) {
console.log('Got a value:' + value);
})
to this
doSomething().then(function(value) {
console.log('Got a value:' + value);
})
那么我们就应该在定义doSomething
函数的时候做出相应的变化
function doSomething(callback) {
var value = 42;
callback(value);
}
to this
function doSomething() {
return {
then: function(callback) {
var value = 42;
callback(42);
}
}
}
Defining the Promise type
首先来看一段定义简单Promise
构造函数的代码:
function Promise(fn) {
var callback = null;
this.then = function(cb) {
callback = cb;
}
function resolve(value) {
callback(value)
}
fn(resolve);
}
然后重写doSomething()
函数:
function doSomething() {
return new Promise(function(resolve) {
var value = 42;
resolve(value);
})
}
重新定义后的doSomething()
函数执行后返回得到一个promise实例
,实例上有then()
方法,可以接受回调函数。
doSomething().then(function(value) {
console.log(value);
})
但是上面的代码会报错(callback is undefined)
,是因为:resolve中的callback要早于then()方法中的callback的赋值操作。
那么对Promise
构造函数稍微处理下,把同步的代码使用setTimeout
来hack下,改变代码的执行顺序,使得resolve
函数中的callback
对value
进行处理前被赋值了。
function Promise(fn) {
var callback = null;
this.then = function(cb) {
callback = cb;
}
function resolve(value) {
setTimeout(function() {
callback(value);
}, 1)
}
fn(resolve);
}
这里通过setTimeout
异步函数改变了代码执行的顺序,确保callback
被调用前已经被赋值成cb
。
重新调用:
doSomething().then(function(value) {
console.log(value);
})
// 42
//正常执行。
但是定义Promise
构造函数的代码还是有问题的,因为如果仅仅是调用then()
方法而注入回调的话,内部的callback
仍然是null
。同样不能正常的执行。
别急,慢慢来。
Promises have state
事实上Promise
是有状态的:
pending
resolved
rejected
pending => resolved
或者 pending => rejected
。状态一旦发生改变,不可逆。接下来,让我们在Promise
的构造函数里面加入state
,使用state
来控制整个代码流程。
function Promise(fn) {
var state = 'pending',
value,
deferred;
function resolve(newValue) {
state = 'resolved';
value = newValue;
if(deferred) {
handle(deferred);
}
}
function handle(onResolved) {
if(state === 'pending') {
deferred = onResolved;
return ;
}
onResolved(value);
}
this.then = function(onResolved) {
handle(onResolved);
}
fn(resolve);
}
代码变的比之前更加复杂。但是现在使用state
来控制代码的流程。then()方法
和resolve()方法
将控制权交给了新的方法handle()
,由handle()
方法来根据state
的值进行流程操作:
如果
state
为pending
状态,即在resolve()
之前调用了then()
方法,那么会将onResolved
回调赋值给一个deferred延迟对象
,deferred对象
将这个回调保存起来,稍后当resolve()
调用时,pending
状态变为resolved
,并调用deferred对象
。如果在
then()
方法前调用resolve()
方法,pending
状态变为resolved
,然后调用then()
里面注入的回调onResolved
.
通过以上的代码,promise
可以任意次数的调用then()
方法:
var promise = doSomething();
promise.then(function(value) {
console.log('Got a value:', value);
});
// 42
promise.then(function(value) {
console.log('Got the some value again:', value);
});
//42
但是这样的Promis
e构造函数还是有问题的,大家可以想象下,在调用resolve()
方法前,调用了很多次的then()
方法,那么只有最后一个then()
方法里面注入的callback
才会有用。解决这个问题的方法就是维持一个deferreds队列
,去保存每次then()
方法注入的回调函数。
Chaining Promises
下面的代码是最普通不过的promise链式调用:
getSomeData()
.then(filterTheData)
.then(processTheData)
.then(displayTheData)
getSomeData()
方法调用后会返回一个promise对象
,这样便可以调用then()
方法,同样这第一个then()
方法调用后也会返回一个promise
对象。这样才能继续调用then()
方法。
then()方法总是返回一个promise。
接下来在代码中加以实现:
function Promise(fn) {
var state = 'pending',
value,
deferred = null;
function resolve(newValue) {
state = 'resolved';
value = newValue;
if(deferred) {
handle(deferred);
}
}
function handle(handler) {
if(state == 'pending') {
deferred = handler;
return;
}
if(!handler.onResolved) {
handler.resolve(value);
return;
}
var ret = handler.onResolved(value);
handler.resolve(ret);
}
this.then = function(onResolved) {
return new Promise(function(resolve) {
handle({
onResolved: onResolved,
resolve: resolve
});
});
};
fn(resolve);
}
在这次的代码中,调用then()
方法后会返回一个新的生成的promise对象
。它具有then()
方法,可以继续调用then()
,并返回一个新生成的promise
对象。如此继续进行下去。这就实现了Promise
链式调用。
再来看看具体的代码实现:resolve()
方法没什么变化,但是handle()
方法接收一个handler
对象。handler
对象有2个属性,一个为onResolved
,then()
方法里面注入的回调函数,用来对传入的上一个promise
传递过来的值进行处理;另一个为resolve
,Promise
构造函数内部定义的resolve()
方法,用来改变Promise
状态以及value
值。
具体分析下handle()
函数:
function handle(handler) {
if(state === 'pending') {
deferred = handler;
return;
}
if(!handler.onResolved) {
handler.resolve(value);
return;
}
var ret = handler.onResolved(value);
handler.resolve(ret);
}
每次调用then()
方法新建一个promise
对象过程当中,handle({onResolved: onResolved, resolve: resolve})
中resolve
属性始终是获得的定义过程中对外部resolve
方法的引用。即上一次的promise
中定义的resolve
.
当then()方法里面注入回调函数时,调用onResolved方法并获得返回值ret,传入resolve方法,改变state的值以及更改promise中需要继续传递下去的值。如果onResolved方法中会返回处理过的值,那么下一个promise能拿到这个值,如果onResolved没有返回,传入下一个promise的为undefined**
doSomething().then(function(result) {
console.log('First result', result);
return 88;
}).then(function(secondResult) {
console.log('second result', secondResult);
})
//the output is
//
//First result 42
//Second result 88
doSomething().then(function(result) {
console.log('First result', result);
}).then(function(secondResult) {
console.log('Second result', secondResult);
})
//now the output is
//First result 42
//Second result undefined
当then()没有注入回调函数时,仍然会调用resolve方法,改变state的值,以及获取上一个promise传递过来的值,并将值传递给下一个promise。
doSomething().then().then(function(result) {
console.log('Got a result', result);
});
//the output is
//
//Got a result 42
主要是得益于handle()
方法中,调用resolve
方法获取从上一个promise
得到的value
以及作为传入下一个promise
的value
:
if(!handler.onResolved) {
handler.resolve(value);
return;
}
再每次调用then()方法的过程都会新建一个pending状态的promise,并通过resolve方法改变状态,如果then()方法中注入了回调函数,并返回了值,那么这个值会一直传递下去,如果没有注入回调函数,resolve方法会获取上一个promise传递过来的值,并作为传入下一个promise的值。即then()方法注入的回调函数是可选的。
通过再次对Promise构造函数
的加强,完成了promise链式调用
的功能。
对于reject的部分过2天加上。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。