LoginSignup
14
14

More than 5 years have passed since last update.

waterfall, parallel再発明

Last updated at Posted at 2012-12-11

async.jsなどにあるwatarfallとparallelを再発明してみた。

Callbacksクラス

まずはCallbacksクラスを作ります。
使い方は簡単で、まず、非同期関数はCallbacksを返すようにします。次に、処理完了時のコールバック関数をdonefailの引数に渡します。そして、非同期関数内で処理が完了したらdoneCallbackfailCallbackを実行するだけです。

var Callbacks = (function() {
  var noop = function(){};
  function Callbacks() {
    this.doneCallback = this.failCallback = noop;
  }
  Callbacks.prototype.done = function(callback) {
    this.doneCallback = callback;
    return this;
  };
  Callbacks.prototype.fail = function(callback) {
    this.failCallback = callback;
    return this;
  };
  return Callbacks;
})();

// msミリ秒後に何か実行
function wait(ms) {
  var callbacks = new Callbacks;
  setTimeout(function() {
    callbacks.doneCallback();
  }, ms);
  return callbacks;
}

wait(1000).done(alert.bind(window, 'hello world'));

// ロードしたら何か実行
function load(url) {
  var callbacks = new Callbacks;
  var xhr = new XMLHttpRequest;
  xhr.open('GET', url);
  xhr.onloadend = function() {
    var status = xhr.status;
    if (status === 200 || status === 206) {
      callbacks.doneCallback(xhr.responseText);
    } else {
      callbacks.failCallback('Load error: ' + status);
    }
  };
  xhr.send();
  return callbacks;
}

load('foo.txt')
.done(function(foo) {
  console.log(foo)
})
.fail(function(e) {
  cansole.log(e);
});

waterfall関数

waterfall関数は引数にCallbacksを返す関数をn個受け取りCallbacksを返します。
そのn個の非同期処理を上から順番に実行して、最後にdonefailの引数に渡した関数を実行します。ちなみにthisのコンテキストは共有されます。

function waterfall(funcs) {
  var callbacks = new Callbacks, context = {};

  funcs = Array.isArray(funcs) ? funcs : Array.prototype.slice.call(arguments);

  function doneCallback() {
    callbacks.doneCallback.apply(context, arguments);
  }

  function failCallback(e) {
    callbacks.failCallback(e);
  }

  // 前から1個ずつ順番に実行していく
  function _waterfall() {
    funcs
    .shift()
    .apply(context, arguments)
    .done(funcs.length > 0 ? _waterfall : doneCallback)
    .fail(failCallback);
  }

  _waterfall();

  return callbacks;
}

waterfall(function() {
  this.hoge = 1;
  return wait(1000);
}, function() {
  console.log(this.hoge);
  console.log('foo');
  return wait(2000);
}, function() {
  console.log('bar');
  return load('foo.txt');
})
.done(function(foo) {
  console.log(foo);
})
.fail(function(e) {
  console.log(e);
});

parallel関数

parallel関数は引数にn個のCallbacksを受け取りCallbacksを返します。
n個の非同期処理が全て完了したときにその結果をdoneの引数に渡した関数から配列として受け取ることができます。失敗時はfailの引数に渡した関数を即座に実行します。

function parallel(callbacksArr) {
  callbacksArr = Array.isArray(callbacksArr) ? callbacksArr : Array.prototype.slice.call(arguments);

  var callbacks = new Callbacks,
    results = [],
    count = callbacksArr.length,
    isError = false;

  function doneCallback(i, result) {
    // エラーがすでに起きている場合は何もしない
    if (isError) return;
    count--;
    results[i] = result;
    // 全て終了したらdoneCallbackを実行
    if (!count) callbacks.doneCallback(results);
  }

  function failCallback(e) {
    isError = true;
    callbacks.failCallback(e);
  }

  callbacksArr.forEach(function(callbacks, i) {
    callbacks
    .done(doneCallback.bind(null, i))
    .fail(failCallback);
  });

  return callbacks;
}

parallel(
  load('foo.txt'),
  load('bar.txt'),
  load('baz.txt')
)
.done(function(texts) {
  texts.forEach(function(text) {
    console.log(text);
  });
})
.fail(function(e) {
  console.log(e);
});

まとめ

waterfallもparallelも意外と簡単に実装できるってことがわかったと思います。
ちなみにwaterfall関数とparallel関数はどちらもCallbacksを返すので、相互に組み合わせることができます。

waterfall(function() {
  return parallel(load('foo.txt'), load('bar.txt'));
}, function(texts) {
  this.texts = texts;
  return parallel(load('hoge.js'), load('fuga.js'));
}, function(scripts) {
  this.scripts = scripts;
  return wait(1000);
})
.done(function() {
  console.log(this.texts);
  console.log(this.scripts);
})
.fail(function(e) {
  console.log(e);
});
14
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
14