操作方法

JSDeferred による非同期処理

Hitoshi Amano <seijro@gmail.com>

Agenda

はじめに

今日は、 JavaScript の非同期処理についてお話しします

非同期処理が必要な場面

しかし、 JavaScript には、スレッドや継続の機能がありません。

なので、非同期処理は煩雑なコードになってしまいがちです。

実際の例

実際の例を見て行きましょう

DOM イベント

element.onclick = function() {
    // 処理
};
element.addEventListener('click', function() {
    // 処理
}, false);

Prototype.js

$(element).observe('click', function() {
    // 処理
});

// 能動的な発火
$(element).fire('click');

jQuery

$(selector).bind('click', function() {
    // 処理
});
$(selector).bind('hoge', function() {
    // 処理
});

// 能動的な発火
$(selector).trigger('hoge');

XMLHttpRequest の返信

var x = new XMLHttpRequest();
x.open('GET', '/hoge.html');
x.send(null);
x.onreadystatechange = function() {
    // 処理
};

Prototype.js

new Ajax.Request('/hoge.html', {
    method: 'get',
    onComplete: function() {
        // 処理
    });

jQuery

$.get('/hoge.html', null, function() {
    // 処理
});

重い処理

var finished = false;
var id = setInterval(function() {
    var t = new Date;
    while (!finished) {
        // ちょっとずつ処理する
        if (t - new Date) break;
    }
    if (finished) clearInterval(id);
}, 0);

アニメーション

var element = $('hoge');
element.style.opacity = 1;
var id = setInterval(function() {
    element.style.opacity -= 0.1;
    if (element.style.opacity <= 0)
        clearInterval(id);
}, 100);

Scriptaculous

$(element).visualEffect('opacity', {
    from: 1,
    to: 0
    afterFinish: function() {
        // 終了処理
    }
});

とにかく

色々なやり方あって覚えるのめんどい

意外と融通効かなかったりする

この非同期処理の機能だけ一つの統一したライブラリ使えばいいんじゃないか

ということで

こういうのをちゃんとキレイに書けるライブラリってないの?

という訳で、 JSDeferred の紹介です。

JSDeferred

JSDeferred とは

JSDeferred の使い方

では、使い方を見て行きましょう。

準備

// Deferred.define を呼び出す
Deferred.define();

// この時点でグローバル変数
// next, call, parallel, wait, loop
// が使えるようになる

これだけで準備完了です

さっそく使ってみましょう

処理を切り離したい 1

var a = funA();
// <- この間にレンダリングを挟みたい
funB(a);

処理を切り離したい 1

next(function() { var a = funA(); }).

next(function() { funB(a); });

// a の宣言は外にしないとダメ

処理を切り離したい 1

var a;
next(function() { a = funA(); }).

next(function() { funB(a); });

// この時点では、まだ非同期化されていない

処理を切り離したい 1

var a;
next(function() { a = funA(); }).
wait(0).
next(function() { funB(a); });

// wait(0) は Java の sleep(0) や yield と同じ

ね?簡単でしょう?

処理を切り離したい 2

for (var i = 0; i < array.length; i ++) {
    // array[i] をどうこうする処理
}

処理を切り離したい 2

loop(array.length, function(i) {
    // array[i] をどうこうする処理

});

// loop 関数を使う
// まだこの時点では処理は切り離されない

処理を切り離したい 2

loop(array.length, function(i) {
    // array[i] をどうこうする処理
    return wait(0);
});

// ここで wait 関数を呼び出す

処理を切り離したい 2

loop({ begin: 0, end: array.length, step: 3 },
                                   function(n, o){
    for (var i = 0; i < o.step; i ++) {
        // array[n+i] をどうこうする処理
    }
    return wait(0);
});

// 3 回に一回だけ切り離したい場合は

ね?簡単でしょう?

処理を切り離したい 3

while (flag) {
    // 処理 1
}
// 処理 2

処理を切り離したい 3

next(function() {
    if (!flag) return;
    // 処理 1
    return next(arguments.callee);
}).
wait(0).next(function() {
    // 処理 2
});

あれ? next の return って次の next に渡されるんじゃなかったの?

return に渡されるのが next とか wait とか loop とかの復帰値だった場合は例外的に扱われる。

処理を同時に実行したい

for (var i = 0; i < 100; i ++) { /* 処理 1 */ }
for (var i = 0; i < 10 ; i ++) { /* 処理 2 */ }
// 処理 3

// 処理 1 と 処理 2 を平行に実行して両方が終わったら
// 処理 3 を実行したい

処理を同時に実行したい

parallel([
    loop(100, function(i) {
        /* 処理 1 */ return wait(0) }),
    loop(10, function(i) {
        /* 処理 2 */ return wait(0) })
]).
next(function() {
    // 処理 3
});

時間を待つ



    // 処理 1



    // 処理 2

時間を待つ

wait(10).
next(function() {
    // 処理 1
}).
wait(10).
next(function() {
    // 処理 2
});

エラーハンドリング

try {
    // 処理 1
}
catch(e) {
    // 処理 2
}

    // 処理 3

エラーハンドリング

next(function() {
    // 処理 1
}).
error(function(e) {
    // 処理 2
}).
next(function() {
    // 処理 3
});

この仕組みを使ってライブラリを作ると...

(作るときは、ちょっとソース読まないと厳しいかも...)

たとえば Ajax とか

get('/hoge.cgi').
error(function(e) {
    // エラー処理
}).
next(function(text) {
    // 処理
});

たとえば JSONP とか

jsonp('/fuga.cgi').
error(function(e) {
    // エラー処理
}).
next(function(text) {
    // 処理
});

たとえば同時実行とかとか

parallel([
    get('/hoge.cgi'),
    jsonp('/fuga.cgi')
]).
error(function(e) {
    /* エラー処理 */ }).
next(function(obj) {
    /* 処理 */ });

という訳で

JSDeferred 便利。

cho45++

まとめ

JSDeferred 使ってみてください。

あと、ソースは数十行なので読んでみてください。すごく勉強になると思います。