今日は、 JavaScript の非同期処理についてお話しします
しかし、 JavaScript には、スレッドや継続の機能がありません。
なので、非同期処理は煩雑なコードになってしまいがちです。
実際の例を見て行きましょう
element.onclick = function() { // 処理 }; element.addEventListener('click', function() { // 処理 }, false);
$(element).observe('click', function() { // 処理 }); // 能動的な発火 $(element).fire('click');
$(selector).bind('click', function() { // 処理 }); $(selector).bind('hoge', function() { // 処理 }); // 能動的な発火 $(selector).trigger('hoge');
var x = new XMLHttpRequest(); x.open('GET', '/hoge.html'); x.send(null); x.onreadystatechange = function() { // 処理 };
new Ajax.Request('/hoge.html', { method: 'get', onComplete: function() { // 処理 });
$.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);
$(element).visualEffect('opacity', { from: 1, to: 0 afterFinish: function() { // 終了処理 } });
色々なやり方あって覚えるのめんどい
意外と融通効かなかったりする
この非同期処理の機能だけ一つの統一したライブラリ使えばいいんじゃないか
こういうのをちゃんとキレイに書けるライブラリってないの?
という訳で、 JSDeferred の紹介です。
では、使い方を見て行きましょう。
// Deferred.define を呼び出す Deferred.define(); // この時点でグローバル変数 // next, call, parallel, wait, loop // が使えるようになる
これだけで準備完了です
さっそく使ってみましょう
var a = funA(); // <- この間にレンダリングを挟みたい funB(a);
next(function() { var a = funA(); }). next(function() { funB(a); }); // a の宣言は外にしないとダメ
var a; next(function() { a = funA(); }). next(function() { funB(a); }); // この時点では、まだ非同期化されていない
var a; next(function() { a = funA(); }). wait(0). next(function() { funB(a); }); // wait(0) は Java の sleep(0) や yield と同じ
ね?簡単でしょう?
for (var i = 0; i < array.length; i ++) { // array[i] をどうこうする処理 }
loop(array.length, function(i) { // array[i] をどうこうする処理 }); // loop 関数を使う // まだこの時点では処理は切り離されない
loop(array.length, function(i) { // array[i] をどうこうする処理 return wait(0); }); // ここで wait 関数を呼び出す
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 回に一回だけ切り離したい場合は
ね?簡単でしょう?
while (flag) { // 処理 1 } // 処理 2
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 });
(作るときは、ちょっとソース読まないと厳しいかも...)
get('/hoge.cgi'). error(function(e) { // エラー処理 }). next(function(text) { // 処理 });
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 使ってみてください。
あと、ソースは数十行なので読んでみてください。すごく勉強になると思います。