GeekFactory

int128.hatenablog.com

非同期処理の直列実行を Array.reduce() で書いてみる

jQuery 1.5から Deferred Object がサポートされて、AJAXのレスポンスは Deferred API を通じて受け取れるようになりました。例えば、下記のように $.post() の結果は .done() もしくは .fail() で受け取れます。

$.post('/reserve', {product: 'iPhone5'}).done(function () {
  console.info('iPhone5を予約しますた');
}).fail(function () {
  console.info('iPhone5を予約できなかった><');
});

これにより、複数のAJAXリクエストをすべて受信してから処理といった制約条件が書きやすくなりました。

var task1 = $.post('/reserve/1').done();
var task2 = $.post('/reserve/2').done();
$.when(task1, task2).done(function () {
  console.info('すべて予約できたお');
});

非同期処理の直列化

では、複数の非同期処理を直列に実行するにはどうすればよいでしょうか。task1が完了したらtask2を実行して、それが完了したらtask3を実行するという流れです。

.done() をネストさせて非同期処理をチェーンさせる方法があります。

var task1 = $.post.bind(null, '/reserve/1');
var task2 = $.post.bind(null, '/reserve/2');
var task3 = $.post.bind(null, '/reserve/3');

task1().done(function () {
  task2().done(function () {
    task3();
  });
});

一般化

n個の非同期処理に一般化するとどうなるでしょうか。

var tasks = [task1, task2, task3, task4, task5, /* ... taskn */];

tasks.reduceRight(function (x, y) {
  return function () {
    return y().done(x);
  };
})();

ここでは Array.reduceRight() を使います。Array.reduceRight() は配列の右側から関数を適用するメソッドです。関数を適用すると以下のようになります。

step 1
[task5, task4] に関数を適用します。結果を仮に f1 とします。
function () {
  return task4().done(task5);
}
step 2
[f1, task3] に関数を適用します。結果を仮に f2 とします。
function () {
  return task3().done(f1);
}
// すなわち
function () {
  return task3().done(function () {
    return task4().done(task5);
  });
}
step 3
[f2, task2] に関数を適用します。結果を仮に f3 とします。
function () {
  return task2().done(f2);
}
// すなわち
function () {
  return task2().done(function () {
    return task3().done(function () {
      return task4().done(task5);
    });
  });
}
step n
繰り返し。

実際に使う場合は、必要に応じて Function.bind() で this コンテキストをバインドするのをお忘れなく。

小一時間ほど考えたのでエントリに残しておきました。