jQuery.Deferredを使って非同期ループしよう!

突然ですが、Deferred使ってますか?

jQuery.DeferredとはjQueryのバージョン1.5から導入された、非同期処理をうまく扱うための標準モジュールです。
使いこなすことで、以下のような効果が見込めます。

  • 非同期処理を連結する際、コールバック地獄から解放される(直列処理、並列処理が可能)
  • エラー処理をうまく記述できる
  • 一連の非同期処理を関数化して再利用しやすくできる

って説明される事が多いかと思います。
利用方法を説明してるサイトもいっぱいあります。

…が非同期に慣れてない方や、とりあえずjQueryは触れるよ!くらいの人だとすごくわかりにくい動きになっています。

そこでjQuery.eachと同じように使えるにプラグインを書いてみました。

jquery-asyncEach.js

if(jQuery) !function($){
	'use strict';
	if( typeof $.asyncEach === 'undefined' ){
		// setTimeoutとDeferredを使い非同期にする関数
		var async = function($callback){
			var $dfd = $.Deferred();
			setTimeout(function(){
				$callback($dfd);
			},0);
			return $dfd.promise();
		};
		// $.asyncEachメソッド本体
		$.asyncEach = function($array,$callback,$thisArg){
			if( typeof $thisArg === 'undefined' ) $thisArg = $array;
			//$arrayが大量だった時に$whenを作るのに時間がかかるのでいきなり非同期化
			return async(function($dfd){
				//$arrayの要素を1個づつ非同期化
				var $when	= $.map($array,function($value,$index){
					return async(function($dfd){
						return $callback.call($thisArg,$dfd,$value,$index);
					});
				});
				//$whenが全てresolveするとdone、ひとつでもrejectするとfailする
				return $.when.apply($,$when)
					.done(function(){
						//argumentsを配列に変換して返す
						var $_ = [];
						$_.push.apply($_,arguments);
						return $dfd.resolve($_);
					})
					.fail(function(){
						var $_ = [];
						$_.push.apply($_,arguments);
						return $dfd.reject($_);
					})
				;
			});
		};
	}
}(jQuery);

テストコード

jQuery.js、jquery-asyncEach.jsの順で読み込んで実行してください。

!function(){
	console.log('最初に表示される');
	$.asyncEach([1,2,3,4,5,6,7,8,9,10],function($dfd,$value,$index){
		//ここに重い処理(…の、かわりにランダムで待たせてから返す)
		var $r = Math.floor( Math.random() * 1050 );
		setTimeout(function(){
			//どちらかを使って返り値を返す
			// $dfd.resolve() = 処理成功
			// $dfd.reject()  = 処理失敗
			return $r <= 1000
				? $dfd.resolve($value * 2)
				: $dfd.reject($value + 'がキモイ');
		},$r);
	}).then(//.done,.failでもいい
		function($result){
			console.log('重い処理が全部終わったら呼ばれる');
			console.log($result);
		},function($result){
			console.log('途中でrejectされたら呼ばれる');
			console.log($result);
		}
	);
	console.log('asyncEachは非同期なので2番目に表示される');
}()