以前写了一篇关于ES6原生Promise的文章。近期又读朴灵的《深刻浅出Node》,里面介绍了一个Promise/Deferred
模式。segmentfault
Promise是解决异步问题的利器。它实际上是一种模式。Promise有三种状态,未完成态、完成态、失败态,相信你们必定不陌生,Promise对象容许使用.then
的形式,将回调放到IO操做等异步方法的主体以外,使代码优美很多。数组
下面我结合《深刻浅出Node》,介绍一下如何用ES5实现Promise/Deferred模式。相信研究完该实现代码以后,咱们会对Promise的理解更进一步。promise
then方法完成回调注册app
Promise/Deferred模式下,Promise对象经过then
方法调用,注册完成态和失败态的回调函数。异步
因为then
方法支持链式回调,所以then
方法的返回值必定也是Promise对象,咱们在此简单的返回自身,也就是this
。async
那么必定有人要问了:then
中的回调函数,可能返回一个新的Promise对象,此后的then
调用是不是在新的Promise对象上调用的呢?函数
答案是:不必定。this
这个问题其实困扰我好久,直到看了Promise的实现代码我才想明白。其实then
方法的调用,只不过是注册了完成态和失败态下的回调函数而已。这些回调函数组成一个回调队列,处理resolve的值。prototype
Promise构造函数注册回调队列code
Promise构造函数,给每一个实例一个queue属性,将then
方法注册的回调队列,保存在Promise实例的回调队列中。
代码
var Promise = function(){ this.queue = []; } Promise.prototype.then = function(fulfilledHandler, unfulfilledHandler){ var handler = {}; if (typeof fulfilledHandler === "function"){ handler.fulfilled = fulfilledHandler; } if (typeof unfulfilledHandler === "function"){ handler.unfulfilled = unfulfilledHandler; } this.queue.push(handler); return this; }
咱们看到,Promise的代码很简单,只是经过then
方法将一系列的回调函数push到队列中而已。Promise实例暴露给用户的也只有一个then
方法。
这样咱们就能够这样调用了:
promise.then(fulfilledFunc1, unfulfilledFunc1) .then(fulfilledFunc2, unfulfilledFunc2) .then(fulfilledFunc3, unfulfilledFunc3)
那么如何进行状态转换呢?下面我就来说一下带有resolve方法(reject方法同理,下面均以resolve举例)的Deferred。
Deferred实例决定Promise实例的状态
每一个Deferred实例的对应一个Promise实例。调用Deferred实例的resolve方法,能使Promise注册的回调队列中的回调函数依次执行。
先写部分代码:
var Deferred = function(){ this.promise = new Promise(); } Deferred.protoype.resolve = function(val){ var handler, value = val; while(handler = this.promise.queue.shift()){ if (handler && handler.fulfilled){ value = handler.fulfiller(value) && value; } } }
这样咱们就能使用Deferred实例返回Promise实例,而且使用Deferred实例的resolve方法来触发Promise实例的完成态回调,而且将上一个回调若是有返回值,咱们将该返回值做为新的resolve值传递给后面的回调。
处理回调方法返回的Promise实例
根据Promise模式,回调函数返回Promise实例时,下一个then()
中的回调处理的是新的Promise实例。
在以前的代码实现中,then
方法注册了一系列的回调函数,这些回调函数应该处理新的promise实例。这里咱们用了一个小技巧,见代码:
Deferred.protoype.resolve = function(val){ var handler, value = val; while(handler = this.promise.queue.shift()){ if (handler && handler.fulfilled){ value = handler.fulfiller(value) && value; // 修改之处在这里: if (value && value.isPromise){ value.queue = this.promise.queue; // 最后再加一个小技巧 this.promise = value; return; } } } }
咱们将返回promise实例以后的回调列表原封不动的注册到返回的promise中,这样就保证以前then
注册的回调队列能继续调用。最后的小技巧可使旧的deferred实例对应新的promise实例,这样能够继续使用deferred.resolve
方法。
为了判断实例是不是Promise实例,这里简单的修改Promise构造函数:
var Promise = function(){ this.queue = []; this.isPromise = true; }
封装callback方法用于异步调用
Promise之因此是解决异步的利器,一方面是then
方法的链式调用,一方面也是由于resolve
方法能够异步调用,触发回调队列。
因为以NodeJS为标志的异步方法其回调函数相似于这样:
asyncFunction(param, function(err, data){ // do something... });
咱们能够封装一个本身的callback方法,用于异步触发resolve方法。
Deferred.prototype.callback = function(err, data){ if (err){ this.reject(err); } this.resolve(data); }
此后咱们能够这样promisify一个异步函数:
var async = function(param){ var defer = new Deferred(); var args = Array.prototype.silce.call(arguments); args.push(defer.callback); asyncFunc.apply(null, args); return defer.promise; }
由上面的promisify思路,咱们写一个更通常化的promisify函数:
var promisify = function(method){ return function(){ var defer = new Deferred(); var args = Array.prototype.silce.call(arguments, 1); args.push(defer.callback); asyncFunc.apply(null, args); return defer.promise; } }
举一个Node中文件操做的例子:
readFile = promisify(fs.readFile); readFile('file1.txt', 'utf8').then(function(file1){ return readFile(file1.trim(), 'utf8'); }).then(function(file2){ console.log(file2); })
俩字:优雅。