Source: src/h5.async.js

/*
 * Copyright (C) 2012-2016 NS Solutions Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * hifive
 */

/* ------ h5.async ------ */
(function() {
	// =========================================================================
	//
	// Constants
	//
	// =========================================================================

	// =============================
	// Production
	// =============================

	/**
	 * h5.async.loopの第一引数に配列以外のものが渡されたときに発生するエラー
	 */
	var ERR_CODE_NOT_ARRAY = 5000;

	/**
	 * h5.async.deferredがcommonFailHandlerの管理のために上書くjQuery.Deferredのメソッド (failコールバックを登録する可能性のある関数)
	 *
	 * @private
	 * @type {Array}
	 */
	var CFH_HOOK_METHODS = ['fail', 'always', 'pipe', 'then'];

	/**
	 * pipeを実装するために使用するコールバック登録メソッド
	 *
	 * @private
	 * @type {Array}
	 */
	var PIPE_CREATE_METHODS = ['done', 'fail', 'progress'];

	/**
	 * pipeを実装するために使用するコールバック登録メソッドに対応するDeferredのコールバック呼び出しメソッド
	 *
	 * @private
	 * @type {Array}
	 */
	var PIPE_CREATE_ACTIONS = ['resolve', 'reject', 'notify'];

	// =============================
	// Development Only
	// =============================

	var fwLogger = h5.log.createLogger('h5.async');

	/* del begin */
	var FW_LOG_H5_WHEN_INVALID_PARAMETER = 'h5.async.when: 引数にpromiseオブジェクトでないものが含まれています。';

	/**
	 * 各エラーコードに対応するメッセージ
	 */
	var errMsgMap = {};
	errMsgMap[ERR_CODE_NOT_ARRAY] = 'h5.async.each() の第1引数は配列のみを扱います。';

	// メッセージの登録
	addFwErrorCodeMap(errMsgMap);
	/* del end */


	// =========================================================================
	//
	// Cache
	//
	// =========================================================================
	/**
	 * argsToArrayのショートカット
	 *
	 * @private
	 */
	var argsToArray = h5.u.obj.argsToArray;
	// =========================================================================
	//
	// Privates
	//
	// =========================================================================
	// =============================
	// Variables
	// =============================

	// thenが新しいプロミス(deferred)を返す(jQuery1.8以降)かどうか
	// jQuery.thenの挙動の確認

	var isThenReturnsNewPromise = (function() {
		var tempDfd = $.Deferred();
		return tempDfd !== tempDfd.then();
	})();

	// =============================
	// Functions
	// =============================
	/**
	 * progres,notify,notifyWithが無ければそれらを追加する
	 *
	 * @private
	 * @param {Deferred} dfd
	 */
	function addProgressFeatureForCompatibility(dfd) {
		// 既にnorify/notifyWithが呼ばれたかどうかのフラグ
		var notified = false;
		// 最後に指定された実行コンテキスト
		var lastNotifyContext = null;
		// 最後に指定されたパラメータ
		var lastNotifyParam = null;
		// progressCallbacksを格納するための配列
		var progressCallbacks = [];

		// progress,notify,notifyWithを追加
		dfd.progress = function(/* var_args */) {
			// progressの引数は、配列でも可変長でも、配列を含む可変長でも渡すことができる
			// 再帰で処理する
			var callbacks = argsToArray(arguments);
			for (var i = 0, l = callbacks.length; i < l; i++) {
				var elem = callbacks[i];
				if (isArray(elem)) {
					dfd.progress.apply(this, elem);
				} else if (isFunction(elem)) {
					if (notified) {
						// 既にnorify/notifyWithが呼ばれていた場合、jQuery1.7以降の仕様と同じにするためにコールバックの登録と同時に実行する必要がある
						var params = lastNotifyParam;
						if (params !== lastNotifyParam) {
							params = wrapInArray(params);
						}
						elem.apply(lastNotifyContext, params);
					} else {
						progressCallbacks.push(elem);
					}
				}
			}
			return this;
		};

		function notify(/* var_args */) {
			notified = true;
			lastNotifyContext = this;
			lastNotifyParam = argsToArray(arguments);
			if (isRejected(dfd) || isResolved(dfd)) {
				// resolve済みまたはreject済みならprogressコールバックは実行しない
				return dfd;
			}
			var args = argsToArray(arguments);
			// progressコールバックが登録されていたら全て実行する
			if (progressCallbacks.length > 0) {
				for (var i = 0, callbackLen = progressCallbacks.length; i < callbackLen; i++) {
					var params = args;
					if (params !== arguments) {
						params = wrapInArray(params);
					}
					// 関数を実行。関数以外は無視。
					isFunction(progressCallbacks[i]) && progressCallbacks[i].apply(this, params);
				}
			}
			return dfd;
		}
		dfd.notify = notify;

		/**
		 * jQueryの公式Doc(2013/6/4時点)だとnotifyWithの第2引数はObjectと書かれているが、
		 * 実際は配列で渡す(jQuery1.7+のnotifyWithと同じ。resolveWith, rejectWithも同じ)。
		 * notifyは可変長で受け取る(公式Docにはオブジェクトと書かれているが、resolve、rejectと同じ可変長)。
		 */
		dfd.notifyWith = function(context, args) {
			// 第2引数がない(falseに評価される)なら、引数は渡さずに呼ぶ
			return !args ? notify.apply(context) : notify.apply(context, args);
		};
	}

	/**
	 * 引数に関数が含まれているか
	 *
	 * @private
	 * @param {Any} arg コールバック登録関数に渡された引数
	 */
	function hasValidCallback(arg) {
		if (!arg) {
			return false;
		}
		arg = wrapInArray(arg);
		for (var i = 0, l = arg.length; i < l; i++) {
			if (isFunction(arg[i])) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 引数に指定されたpromiseまたはdeferredオブジェクトに対してコールバック登録関数をフックし、commonFailHandlerの機能を追加する。
	 * 既にフック済みのもの(prev)があればprevが持っているものに差し替える
	 *
	 * @private
	 * @param {Deferred|Promise} promise DeferredまたはPromise
	 * @param {Deferred} rootDfd 元のDeferred
	 *            既にフック済みのDeferredオブジェクト。第一引数がPromiseで、元のdeferredでフック済みならそっちのメソッドに差し替える
	 * @returns CFH機能を追加したDeferredまたはPromise
	 */
	function toCFHAware(promise, rootDfd) {
		// すでにtoCFHAware済みなら何もしないでpromiseを返す
		if (promise._h5UnwrappedCall) {
			return promise;
		}

		// progressを持っているか
		var hasNativeProgress = !!promise.progress;

		// 引数がDeferredオブジェクト(!=プロミスオブジェクト)の場合、
		// progress/notify/notifyWithがないなら追加。
		// jQuery1.6.x でもprogress/notify/notifyWithを使えるようにする。
		if (!hasNativeProgress && !isPromise(promise)) {
			addProgressFeatureForCompatibility(promise);
		} else if (rootDfd) {
			// rootDfdが指定されていればrootDfd.progressでpromise.progressを上書き
			promise.progress = rootDfd.progress;
		}

		// failコールバックが1つ以上登録されたかどうかのフラグ
		var existFailHandler = false;
		//commonFailHandlerが発火済みかどうかのフラグ
		var isCommonFailHandlerFired = false;

		// ---------------------------------------------
		// 以下書き換える(フックする)必要のある関数を書き換える
		// ---------------------------------------------

		// jQueryが持っているもともとのコールバック登録メソッドを保持するオブジェクト
		var originalMethods = {};
		// フックしたメソッドを保持するオブジェクト
		var hookMethods = {};

		/**
		 * 指定されたメソッドを、フックされたコールバック登録関数を元に戻してから呼ぶ
		 *
		 * @private
		 * @memberOf Deferred
		 * @param {String} method メソッド名
		 * @param {Array|Any} args メソッドに渡す引数。Arrayで複数渡せる。引数1つならそのまま渡せる。
		 * @returns メソッドの戻り値
		 */
		promise._h5UnwrappedCall = rootDfd ? rootDfd._h5UnwrappedCall : function(method, args) {
			args = wrapInArray(args);
			// originalに戻す
			$.extend(promise, originalMethods);
			// originalに戻した状態でmethodを実行
			var ret = promise[method].apply(this, args);
			// フックされたものに戻す
			$.extend(promise, hookMethods);

			return ret;
		};

		// commonFailHandlerのフラグ管理のために関数を上書きするための関数
		function override(method) {
			if (rootDfd) {
				if (!rootDfd[method]) {
					return;
				}
				promise[method] = rootDfd[method];
				return;
			}
			var originalFunc = promise[method];
			originalMethods[method] = originalFunc;
			promise[method] = (function(_method) {
				return function() {
					if (!existFailHandler) {
						// failコールバックが渡されたかどうかチェック
						var failArgs = argsToArray(arguments);
						if (method === 'then' || method === 'pipe') {
							// thenまたはpipeならargの第2引数を見る
							failArgs = failArgs[1];
						}
						if (hasValidCallback(failArgs)) {
							existFailHandler = true;
						}
					}
					// オリジナルのコールバック登録メソッドを呼ぶ
					return promise._h5UnwrappedCall.call(this, method, argsToArray(arguments));
				};
			})(method);
			hookMethods[method] = promise[method];
		}

		// failコールバックを登録する可能性のある関数を上書き
		for (var i = 0, l = CFH_HOOK_METHODS.length; i < l; i++) {
			var prop = CFH_HOOK_METHODS[i];
			if (promise[prop]) {
				// cfhの管理をするための関数でオーバーライド
				override(prop);
			}
		}


		// pipeは戻り値が呼び出したpromise(またはdeferred)と違うので、
		// そのdeferred/promiseが持つメソッドの上書きをして返す関数にする。
		// jQuery1.6以下にない第3引数でのprogressコールバックの登録にも対応する。
		// rootDfdがあればrootDfd.pipeを持たせてあるので何もしない。
		if (promise.pipe && !rootDfd) {
			promise.pipe = function() {
				// pipeを呼ぶとpipeに登録した関数がプロミスを返した時にfailハンドラが内部で登録され、
				// そのプロミスについてのCommonFailHandlerが動作しなくなる (issue #250)
				// (1.8以降の動作の場合thenも同じ)
				// そのため、pipeはFW側で実装する

				// 新しくDeferredを生成する
				var newDeferred = h5.async.deferred();

				// コールバックの登録
				var fns = argsToArray(arguments);

				for (var i = 0, l = PIPE_CREATE_METHODS.length; i < l; i++) {
					var that = this;
					(function(fn, method, action) {
						var isFunc = isFunction(fn);
						// 登録するコールバック
						function callback(/* var_args */) {
							if (!isFunc) {
								// 関数で無かった場合は、渡された引数を次のコールバックにそのまま渡す
								newDeferred[action + 'With'](this, arguments);
								return;
							}
							var ret = fn.apply(this, arguments);
							if (ret && isFunction(ret.promise)) {
								toCFHAware(ret);
								// コールバックが返したプロミスについてコールバックを登録する
								ret.done(newDeferred.resolve);
								// _h5UnwrappedCallを使って、CFHの挙動を阻害しないようにfailハンドラを登録
								ret._h5UnwrappedCall('fail', newDeferred.reject);
								// jQuery1.6以下でh5を使わずに生成されたプロミスならprogressはないので、
								// progressメソッドがあるかチェックしてからprogressハンドラを登録
								isFunction(ret.progress) && ret.progress(newDeferred.notify);
							} else {
								// 戻り値はプロミスでなかった場合、戻り値を次のコールバックに渡す
								newDeferred[action + 'With'](this, [ret]);
							}
						}

						// コールバックを登録
						// fnが関数でないかつmethodがfailの場合は、CFHの動作を阻害しないようにfailハンドラを登録するため、
						// _h5UnwrappedCallを使う
						if (!isFunc && method === 'fail') {
							that._h5UnwrappedCall(method, callback);
						} else {
							that[method](callback);
						}
					})(fns[i], PIPE_CREATE_METHODS[i], PIPE_CREATE_ACTIONS[i]);
				}
				return newDeferred.promise();
			};
			hookMethods.pipe = promise.pipe;
		}

		// thenは戻り値が呼び出したpromise(またはdeferred)と違う場合(jQuery1.8以降)、
		// そのdeferred/promiseが持つメソッドの上書きをして返す関数にする
		// jQuery1.6対応で、第3引数にprogressFilterが指定されていればそれを登録する
		// rootDfdがあればrootDfd.thenを持たせてあるので何もしない
		if (promise.then && !rootDfd) {
			var then = promise.then;
			// 1.8以降の場合 thenはpipeと同じで、別のdeferredに基づくpromiseを生成して返す(then===pipe)
			promise.then = isThenReturnsNewPromise ? promise.pipe : function(/* var_args */) {
				// 1.7以前の場合
				// jQuery1.7以前は、thenを呼んだ時のthisが返ってくる(deferredから呼んだ場合はdeferredオブジェクトが返る)。
				var args = arguments;
				var ret = then.apply(this, args);

				// 第3引数にprogressFilterが指定されていて、かつprogressメソッドがjQueryにない(1.6以前)場合
				// promise.progressに登録する
				if (!hasNativeProgress && hasValidCallback(args[2])) {
					promise.progress.call(promise, args[2]);
				}
				// そのままthis(=ret)を返す
				return ret;
			};
			hookMethods.then = promise.then;
		}

		// reject/rejectWith
		function createReject(rejectFunc) {
			return function(/* var_args */) {
				var commonFailHandler = h5.settings.commonFailHandler;
				// failコールバックが1つもない、かつcommonFailHandlerがある場合は、commonFailHandlerを登録する
				if (!existFailHandler && commonFailHandler && !isCommonFailHandlerFired) {
					promise.fail.call(this, commonFailHandler);
					isCommonFailHandlerFired = true;
				}
				return rejectFunc.apply(this, arguments);
			};
		}

		// reject
		if (promise.reject) {
			if (rootDfd && rootDfd.reject) {
				promise.reject = rootDfd.reject;
			} else {
				promise.reject = createReject(promise.reject);
			}
		}

		// rejectWith
		if (promise.rejectWith) {
			if (rootDfd && rootDfd.rejectWith) {
				promise.rejectWith = rootDfd.rejectWith;
			} else {
				promise.rejectWith = createReject(promise.rejectWith);
			}
		}

		// promise
		if (promise.promise) {
			if (rootDfd && rootDfd.promise) {
				promise.promise = rootDfd.promise;
			} else {
				var originalPromise = promise.promise;
				promise.promise = function(/* var_args */) {
					// toCFHAwareで上書く必要のある関数を上書いてから返す
					return toCFHAware(originalPromise.apply(this, arguments), promise);
				};
			}
		}
		return promise;
	}

	// =========================================================================
	//
	// Body
	//
	// =========================================================================
	/**
	 * 登録された共通のエラー処理(<a href="h5.settings.html#commonFailHandler">h5.settings.commonFailHandler</a>)を実行できるDeferredオブジェクトを返します。<br>
	 * Deferredに notify() / notifyWith() / progress() メソッドがない場合は、追加したオブジェクトを返します。
	 *
	 * @returns {Deferred} Deferredオブジェクト
	 * @name deferred
	 * @function
	 * @memberOf h5.async
	 */
	var deferred = function() {
		var rawDfd = $.Deferred();
		return toCFHAware(rawDfd);

	};

	/**
	 * オブジェクトがPromiseオブジェクトであるかどうかを返します。<br />
	 * オブジェクトがDeferredオブジェクトの場合、falseが返ります。
	 *
	 * @param {Object} object オブジェクト
	 * @returns {Boolean} オブジェクトがPromiseオブジェクトであるかどうか
	 * @name isPromise
	 * @function
	 * @memberOf h5.async
	 */
	var isPromise = function(object) {
		return !!object && object.done && object.fail && !object.resolve && !object.reject;
	};

	/**
	 * 指定された回数ごとにループを抜けブラウザに制御を戻すユーティリティメソッドです。<br>
	 * また、callbackで渡された関数が{Promise}を返した場合、その{Promise}が終了するまで次のループの実行を待機します。
	 *
	 * @param {Any[]} array 配列
	 * @param {Function} callback コールバック関数。<br />
	 *            コールバックには引数として現在のインデックス、現在の値、ループコントローラが渡されます。<br />
	 *            callback(index, value, loopControl) <br />
	 *            loopControlは以下の3つのメソッドを持っています。<br />
	 *            <ul>
	 *            <li>pause - 処理の途中でポーズをかけます。</li>
	 *            <li>resume - ポーズを解除し処理を再開します。</li>
	 *            <li>stop - 処理を中断します。1度stopで中断すると再開することはできません。</li>
	 *            </ul>
	 * @param {Number} [suspendOnTimes=20] 何回ごとにループを抜けるか。デフォルトは20回です。
	 * @returns {Promise} Promiseオブジェクト
	 * @name loop
	 * @function
	 * @memberOf h5.async
	 */
	var loop = function(array, callback, suspendOnTimes) {
		if (!isArray(array)) {
			throwFwError(ERR_CODE_NOT_ARRAY);
		}
		var dfd = deferred();
		// 何回ごとにループを抜けるか。デフォルトは20回
		var st = $.type(suspendOnTimes) === 'number' ? suspendOnTimes : 20;
		var index = 0;
		var len = array.length;
		var execute, loopControl = null;
		var each = function() {
			if (index === len) {
				dfd.resolve(array);
				return;
			}
			var ret = callback.call(array, index, array[index], loopControl);
			index++;
			if (isPromise(ret)) {
				ret.done(function() {
					execute();
				}).fail(function() {
					dfd.reject(array);
				});
			} else {
				execute();
			}
		};
		var async = function() {
			setTimeout(function() {
				var i = index - 1;
				if (index > 0) {
					dfd.notify({
						data: array,
						index: i,
						value: array[i]
					});
				}
				each();
			}, 0);
		};
		var pause = false;
		execute = function() {
			if (pause) {
				return;
			}
			index % st === 0 ? async() : each();
		};
		var stopFlag = false;
		loopControl = {
			resume: function() {
				if (!stopFlag && pause) {
					pause = false;
					execute();
				}
			},
			pause: function() {
				pause = true;
			},
			stop: function() {
				stopFlag = true;
				dfd.resolve(array);
			}
		};
		async();
		return dfd.promise();
	};

	/**
	 * 引数に指定した1つ以上のPromiseオブジェクトに基づいて、コールバックメソッドを実行します。
	 * <p>
	 * 引数に指定されたPromiseオブジェクトの挙動によって、以下のような処理を実行します。<br>
	 * <ul>
	 * <li>引数に指定されたPromiseオブジェクトのうち、1つでもreject()が実行されると、failコールバックを実行します。</li>
	 * <li>引数に指定されたすべてのPromiseオブジェクトの全てでresolve()が実行されると、doneコールバックを実行します。</li>
	 * <li>引数に指定されたPromiseオブジェクトでnotify()が実行されると、progressコールバックを実行します。</li>
	 * </ul>
	 * 本メソッドはjQuery.when()と同様の機能を持っており、同じように使うことができます。<br>
	 * ただし、以下のような違いがあります。
	 * <h4>jQuery.when()と相違点</h4>
	 * <ul>
	 * <li>failコールバックが未指定の場合、共通のエラー処理(<a
	 * href="./h5.settings.html#commonFailHandler">commonFailHandler</a>)を実行します。(※
	 * whenに渡したpromiseについてのcommonFailHandlerは動作しなくなります。)</li>
	 * <li>jQuery1.6.xを使用している場合、jQuery.when()では使用できないnotify/progressの機能を使用することができます。ただし、この機能を使用するには<a
	 * href="h5.async.html#deferred">h5.async.deferred()</a>によって生成されたDeferredのPromiseオブジェクトを引数に指定する必要があります。<br>
	 * </li>
	 * <li>引数の指定方法について、jQuery.when()は可変長のみなのに対し、本メソッドは可変長またはPromiseオブジェクトを持つ配列で指定することができます。</li>
	 * </ul>
	 * <h4>引数の指定方法</h4>
	 * 配列または可変長で、複数のPromiseオブジェクトを渡すことができます。<br>
	 * 例)
	 * <ul>
	 * <li>h5.async.when(p1, p2, p3); </li>
	 * <li>h5.async.when([p1, p2, p3]); </li>
	 * </ul>
	 * Promiseオブジェクト以外を渡した時は無視されます。<br>
	 * また、可変長と配列の組み合わせで指定することはできません。<br>
	 * <ul>
	 * <li>h5.async.when(p1, [p2, p3], p4);</li>
	 * </ul>
	 * のようなコードを書いた時、2番目の引数は「配列」であり「Promise」ではないので無視され、p1とp4のみ待ちます。<br>
	 * <br>
	 * また、配列が入れ子になっていても、再帰的に評価はしません。<br>
	 * <ul>
	 * <li>h5.async.when([pi, [p2, p3], p4])</li>
	 * </ul>
	 * と書いても、先の例と同様p1とp4のみ待ちます。
	 *
	 * @param {Promise} var_args Promiseオブジェクト(可変長または配列で複数のPromiseを指定する)
	 * @returns {Promise} Promiseオブジェクト
	 * @name when
	 * @function
	 * @memberOf h5.async
	 */
	var when = function(/* var_args */) {
		var args = argsToArray(arguments);

		if (args.length === 1 && isArray(args[0])) {
			args = args[0];
		}
		var len = args.length;

		/* del begin */
		// 引数にpromise・deferredオブジェクト以外があった場合はログを出力します。
		for (var i = 0; i < len; i++) {
			// DeferredもPromiseも、promiseメソッドを持つので、
			// promiseメソッドがあるかどうかでDeferred/Promiseの両方を判定しています。
			if (!args[i] || !(args[i].promise && isFunction(args[i].promise))) {
				fwLogger.info(FW_LOG_H5_WHEN_INVALID_PARAMETER);
				break;
			}
		}
		/* del end */

		// $.when相当の機能を実装する。
		// 引数が一つでそれがプロミスだった場合は$.whenはそれをそのまま返しているが、
		// h5.async.whenではCFHAwareでprogressメソッドを持つpromiseを返す必要があるため、
		// 引数がいくつであろうと、新しくCFHAwareなdeferredオブジェクトを生成してそのpromiseを返す。
		var dfd = h5.async.deferred();
		var whenPromise = dfd.promise();

		// $.whenを呼び出して、dfdと紐づける
		var jqWhenRet = $.when.apply($, args).done(
				function(/* var_args */) {
					// jQuery1.7以下では、thisが$.whenの戻り値の元のdeferredになる。
					// (resolveWithで呼んでも同様。指定したコンテキストは無視される。)
					// そうなっていたら、thisを$.whenに紐づいたdeferredではなく、h5.async.whenのdeferredに差し替える
					dfd.resolveWith(this && this.promise && this.promise() === jqWhenRet ? dfd
							: this, argsToArray(arguments));
				}).fail(function(/* var_args */) {
			dfd.rejectWith(this, argsToArray(arguments));
		});

		// progressがある(jQuery1.7以降)ならそのままprogressも登録
		if (jqWhenRet.progress) {
			jqWhenRet.progress(function(/* ver_args */) {
				// jQuery1.7では、thisが$.whenの戻り値と同じインスタンス(プロミス)になる。
				// (notifyWithで呼んでも同様。指定したコンテキストは無視される。)
				// thisが$.whenの戻り値なら、h5.async.whenの戻り値のプロミスに差し替える
				dfd.notifyWith(this === jqWhenRet ? whenPromise : this, argsToArray(arguments));
			});
		} else {
			// progressがない(=jQuery1.6.x)なら、progress機能を追加

			// progressの引数になる配列。
			// pValuesにはあらかじめundefinedを入れておく($.whenと同じ。progressフィルタ内のarguments.lengthは常にargs.lengthと同じ)
			var pValues = [];
			for (var i = 0; i < len; i++) {
				pValues[i] = undefined;
			}
			function progressFunc(index) {
				// args中の該当するindexに値を格納した配列をprogressコールバックに渡す
				return function(value) {
					pValues[index] = arguments.length > 1 ? argsToArray(arguments) : value;
					// jQuery1.6では、jQuery1.7と同様の動作をするようにする。
					// thisはh5.async.whenの戻り値と同じ。
					dfd.notifyWith(whenPromise, pValues);
				};
			}
			for (var i = 0; i < len; i++) {
				var p = args[i];
				// progressはjQuery1.6で作られたdeferred/promiseだとないので、あるかどうかチェックして呼び出す
				if (p && isFunction(p.promise) && p.progress) {
					if (len > 1) {
						p.progress(progressFunc(i));
					} else {
						// 引数が1つなら、notifyで渡された引数は配列化せず、そのままwhenのprogressへスルーさせる
						p.progress(function(/* var_args */) {
							// thisはh5.async.whenの戻り値と同じ。
							dfd.notifyWith(whenPromise, argsToArray(arguments));
						});
					}
				}
			}
		}
		return whenPromise;
	};

	// =============================
	// Expose to window
	// =============================

	/**
	 * @namespace
	 * @name async
	 * @memberOf h5
	 */
	h5.u.obj.expose('h5.async', {
		deferred: deferred,
		when: when,
		isPromise: isPromise,
		loop: loop
	});

})();