みなさんこんにちは!
閃光部おばらです。

ajax やアニメーション等の非同期処理を行う際、Deferred という概念を用いると非常に作りやすくなります。
Deferred の機能を提供するライブラリは多々ありますが、
今回は必要最低限の機能を持つ Deferred をフルスクラッチしてみましょう。


仕様

今回作成する Deferred の仕様は以下の通りとします。

・【待機中】と【解決】の二つの内部状態を持ち得る。
・初期状態は【待機中】。
・ユーザーは(メソッドを呼び出すことにより)内部の状態を【待機中】から【解決】に一度だけ変更可能。
・一度、内部状態を【解決】に変更した後は、他の状態に変更不可。

・ユーザーは(メソッドを呼び出すことにより)任意の数のコールバック関数を登録(予約)できる。
・登録時の内部状態が【待機中】の場合、コールバック関数はキューに追加される。
(【待機中】から【解決】に変更されたら、キュー内のコールバック関数が順番に実行される。)
・登録時の内部状態が【解決】の場合、コールバック関数は即座に実行される。


インターフェイス

インターフェイスは jQuery の $.Deferred を参考に以下のようにします。

// インスタンスを生成
var deferred = new Deferred;

// コールバックを登録
deferred.done(function() {
    console.log("foo");
});

// コールバックを実行
deferred.resolve();

// コールバックを登録&即実行
deferred.done(function() {
    console.log("bar");
});

まずクラスベースっぽくするため new でインスタンスを生成できるようにします。
また簡単のため、メソッドは resolve() と done() のみとします。
多くの場合この二つで十分でしょう。


実装

それでは早速実装していきましょう。
prototypeを使用すると複雑になる&プライベートな変数を実現しづらいため、
多少のメモリ効率の悪さには目をつぶり、モジュールパターンで実装します。

なお、メソッドパターンの場合、実際には new する必要はないのですが、
わかりやすさのため new でインスタンスを生成するものとします。

まずはコンストラクタ関数(にあたるもの)から

function Deferred() {
    ;
}

次にパブリックなメソッド resolve() と done() を定義します。

/**
 *
 */
function Deferred() {
    /*-------------------------------------------
        PUBLIC
    -------------------------------------------*/
    function resolve() {
        ;
    }
    function done() {
        ;
    }
}

パブリックなメソッド done() と resolve() は ユーザーが呼び出せるように return でエクスポートしましょう。

/**
 *  @return {Object.<function>}
 */
function Deferred() {
    /*-------------------------------------------
        PUBLIC
    -------------------------------------------*/
    function resolve() {
        ;
    }
    function done() {
        ;
    }
    /*-------------------------------------------
        EXPORT
    -------------------------------------------*/
    return {
        resolve : resolve,
        done    : done
    };
}

次にプライベート変数を作成します。

最低限必要なのは、
1) 内部状態を格納する変数(フラグ) _isResolved と、
2) コールバックを格納する配列(キュー) _queue です。

_isResolve の初期値は false とします。
_queue の初期値は空の配列とします。

※プライベートな変数、関数の識別子は慣習に基づき _ で始めるものとします。

/**
 *  @return {Object.<function>}
 */
function Deferred() {
    /*-------------------------------------------
        PRIVATE
    -------------------------------------------*/
    var _isResolved = false,
        _queue      = [];
    
    /*-------------------------------------------
        PUBLIC
    -------------------------------------------*/
    function resolve() {
        ;
    }
    function done() {
        ;
    }
    /*-------------------------------------------
        EXPORT
    -------------------------------------------*/
    return {
        resolve : resolve,
        done    : done
    };
}

さて、これで最低限必要な変数、メソッドが用意できました。
ここからいよいよメソッドの実装に入ります。

resolve()

resolve() で行う処理は以下の二つです。
1)プライベート変数 _isResolved を true に変更する。
2)キューを消化する。

/**
 *  @return {undefined}
 */
function resolve() {
    var len = _queue.length,
        i   = 0;
    
    _isResolved = true;
    for (; i < len; ++i) {
        _queue[i]();
    }
}

※簡単のため、キューの中身が関数かどうかといった型チェックは省いています。

なお今回は気にしなくてOKですが、
キューをループで回す際にループ中に(ユーザーによって)キューが変更される可能性がある場合は
以下のようにキューの浅いコピーを作成し使用すると良いでしょう。

var copy = [].concat(_queue),
    len  = copy.length,
    i    = 0;

for (; i < len; ++i) {
    // do something...
}

done()

done() で行う処理は _isResolved の値により、異なります。

1) _isResolved === true の場合
コールバック関数を即、実行する。

2) _isResolved === false の場合
_queueにコールバック関数を追加する。

実装は以下のようになります。

/**
 *  @param  {function} func
 *  @return {undefined}
 */
function done(func) {
    if (_isResolved === true) {
        func();
    } else if (_isResolved === false) {
        _queue.push(func);
    }
}

もう少し簡潔に書くと...

/**
 *  @param  {function} func
 *  @return {undefined}
 */
function done(func) {
    _isResolved ? func() : _queue.push(func);
}

ここまでをまとめると、以下のようになります。

/**
 *  @return {Object.<function>}
 */
function Deferred() {
    /*-------------------------------------------
        PRIVATE
    -------------------------------------------*/
    var _isResolved = false,
        _queue      = [];
    
    /*-------------------------------------------
        PUBLIC
    -------------------------------------------*/
    /**
     *  @return {undefined}
     */
    function resolve() {
        var len = _queue.length,
            i   = 0;
        
        _isResolved = true;
        for (; i < len; ++i) {
            _queue[i]();
        }
    }
    /**
     *  @param  {function} func
     *  @return {undefined}
     */
    function done(func) {
        _isResolved ? func() : _queue.push(func);
    }
    /*-------------------------------------------
        EXPORT
    -------------------------------------------*/
    return {
        resolve : resolve,
        done    : done
    };
}

ここでもう少し簡潔に書けないか考察してみましょう。

_isResolved と _queue の関係に注目します。
_queueが必要となるのは _isResolved が false の場合のみです。
また、_isResolved は一度 true になると、再度 false に戻ることはありません。
(その手段がありません)

そこで _isResolved を無くし、
代わりに変数 _queue を内部状態を示すフラグとしても使うことにしましょう。

/**
 *  @return {Object.<function>}
 */
function Deferred() {
    /*-------------------------------------------
        PRIVATE
    -------------------------------------------*/
    var _queue = [];
    
    /*-------------------------------------------
        PUBLIC
    -------------------------------------------*/
    /**
     *  @return {undefined}
     */
    function resolve() {
        var arr = _queue,
            len = arr.length,
            i   = 0;
        
        _queue = null;
        for (; i < len; ++i) {
            arr[i]();
        }
    }
    /**
     *  @param  {function} func
     *  @return {undefined}
     */
    function done(func) {
        _queue ? _queue.push(func) : func();
    }
    /*-------------------------------------------
        EXPORT
    -------------------------------------------*/
    return {
        resolve : resolve,
        done    : done
    };
}

これで完成です。
このように Deferred の基本的な機能はわずか(コメントを除くと)20行足らずで実装可能です。


使用例

最後に、今回作成した Deferred を用い、CSSアニメーション用のユーティリティ関数を作成してみましょう。
※WebKitのみ

/**
 *  @param  {string} cssSelector
 *  @return {Object.<function>}
 */
function $(cssSelector) {
    /*-------------------------------------------
        PRIVATE
    -------------------------------------------*/
    var _element  = document.querySelector(cssSelector),
        _deferred = new Deferred;
    
    /*-------------------------------------------
        INIT
    -------------------------------------------*/
    _deferred.resolve();
    
    /*-------------------------------------------
        PRIVATE
    -------------------------------------------*/
    /**
     *  @private
     *  @param  {Deferred} prevDeferred
     *  @param  {number}   x
     *  @param  {number}   y
     *  @return {Object.<function>}
     */
    function _to(prevDeferred, x, y) {
        var nextDeferred = new Deferred;
        
        prevDeferred.done(render);
        
        function handleEnd() {
            _element.removeEventListener("webkitTransitionEnd", handleEnd, false);
            nextDeferred.resolve();
        }
        function render() {
            _element.addEventListener("webkitTransitionEnd", handleEnd, false);
            _element.offsetTop;// force a reflow
            _element.style.cssText += ";" +
                "-webkit-transform-style: preserve-3d;" + // switch to 3d
                "-webkit-transition: -webkit-transform .5s ease-in-out;" + // enable transition
                "-webkit-transform: translate(" + x + "px," + y + "px);";
            
        }
        function to(x, y) {
            return _to(nextDeferred, x, y);
        }
        return {
            to   : to,
            done : nextDeferred.done
        };
    }
    /*-------------------------------------------
        PUBLIC
    -------------------------------------------*/
    /**
     *  @param  {number}   x
     *  @param  {number}   y
     *  @return {Object.<function>}
     */
    function to(x, y) {
        return _to(_deferred, x, y);
    }
    /*-------------------------------------------
        EXPORT
    -------------------------------------------*/
    return {
        to   : to,
        done : _deferred.done
    };
}

これを Deferred 無しで作るとしたら。。。ゾッとしますね><
まさに、Deferred 様々です。

使用方法は以下の通りです。

$("#hoge").
    to(  0, 100).
    to(100, 100).
    to(100, 200).
    to(200, 200).
    done(function() {
        alert("オワタ\(^o^)/");
    });

以下にサンプルコードを置きましたので、ご覧ください!

追記:続きはこちら!!→Deferred を作ろう!#2

HTML5飯