以前寫了一篇關於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); })
倆字:優雅。