LoginSignup
3

More than 5 years have passed since last update.

Pure Javascriptで学ぶ非同期プログラミング

Last updated at Posted at 2017-05-02

PromiseやRxJSが出る前でもJavascript業界では非同期プログラミングは重要な問題でした。
何故重要かって言うと、2つ以上の非同期入力の処理が極めて難しいからです。

例えば1つの非同期を処理するにはcallbackでも大丈夫です。

var clickCount = 0;
$("button").on("click", function(e){
  clickCount++;
  if (clickCount == 10) {
    console.log("You clicked 10 times!");
    clickCount = 0;
  }
});

このシナリオをちょっと改善して、非同期入力を2つにしてみましょう。

var clickCount = 0;
$("button").on("click", function(e){
  clickCount++;
  if (clickCount == 10) {
    console.log("You clicked 10 times!");
    clickCount = 0;
  }
});

setInterval(function() {
  clickCount--;
  if (clickCount <= 0) {
    clickCount = 0;
    console.log("You need to click more quickly");
  }
}, 500);

ここで終わったらなんの問題もないんでしょうが、機能をすこし追加してみましょう。
5になった瞬間メッセージを表示する機能です。

var clickCount = 0;
$("button").on("click", function(e){
  clickCount++;
  if (clickCount == 5) {
    console.log("Just left five more clicks!");
  }
  if (clickCount == 10) {
    console.log("You clicked 10 times!");
    clickCount = 0;
  }
});

setInterval(function() {
  clickCount--;
  if (clickCount == 5) {
    alert("Just left five more clicks!");
  }
  if (clickCount <= 0) {
    clickCount = 0;
    alert("You need to click more quickly");
  }
}, 500);

あれれ、重複するコードが出ましたね。これじゃコードの管理が難しくなりそうですので、ちょっと整理しましょう。

var clickCount = 0;
$("button").on("click", function(e){
  clickCount++;
  countCheck();
});

setInterval(function() {
  clickCount--;
  countCheck();
}, 500);

function countCheck() {
  if (clickCount == 5) {
    console.log("Just left five more clicks!");
  }
  if (clickCount <= 0) {
    clickCount = 0;
    console.log("You need to click more quickly");
  }
  if (clickCount == 10) {
    console.log("You clicked 10 times!");
    clickCount = 0;
  }
}

clickCountがバラバラなので、わけがわからなくなりそうですね。まとめてみましょう。

var clickCount = 0;
var CLICK_UP = 0, CLICK_DOWN = 1;

$("button").on("click", function(e){
  countCheck(CLICK_UP);
});

setInterval(function() {
  countCheck(CLICK_DOWN);
}, 500);

function countCheck(clickState) {
  if (clickState == CLICK_UP) clickCount++;
  else clickCount--;

  if (clickCount == 5) {
    console.log("Just left five more clicks!");
  }
  if (clickCount <= 0) {
    clickCount = 0;
    console.log("You need to click more quickly");
  }
  if (clickCount == 10) {
    console.log("You clicked 10 times!");
    clickCount = 0;
  }
}

もしcountCheck()の中でcountCheckを呼ぶようになったらリカーシブになるので、非同期化しましょう。

var clickCount = 0;
var CLICK_UP = 0, CLICK_DOWN = 1;

$("button").on("click", function(e){
  setTimeout(function(){countCheck(CLICK_UP);}, 0);
});

setInterval(function() {
  setTimeout(function(){countCheck(CLICK_DOWN);}, 0);
}, 500);

function countCheck(clickState) {
  if (clickState == CLICK_UP) clickCount++;
  else clickCount--;

  if (clickCount == 5) {
    console.log("Just left five more clicks!");
  }
  if (clickCount <= 0) {
    clickCount = 0;
    console.log("You need to click more quickly");
  }
  if (clickCount == 10) {
    console.log("You clicked 10 times!");
    clickCount = 0;
  }
}

setTimeoutが長いんですね。簡単なラッパーを作って、もうちょっと綺麗にしましょう。

function UsagiAsync(_func) {
  var func = _func;
  return {
    next: function(state){
    (function(f){setTimeout(function(){f(state)}, 0);})(func);
  }
}

var clickCount = 0;
var CLICK_UP = 0, CLICK_DOWN = 1;

var countCheckAsync = UsagiAsync(countCheck);

$("button").on("click", function(e){
  countCheckAsync(CLICK_UP);
});

setInterval(function() {
  countCheckAsync(CLICK_DOWN);
}, 500);

function countCheck(clickState) {
  if (clickState == CLICK_UP) clickCount++;
  else clickCount--;

  if (clickCount == 5) {
    console.log("Just left five more clicks!");
  }
  if (clickCount <= 0) {
    clickCount = 0;
    console.log("You need to click more quickly");
  }
  if (clickCount == 10) {
    console.log("You clicked 10 times!");
    clickCount = 0;
  }
}

ほぼ終わりです。複数の非同期シナリオに対応するように変更してみましょう。

function UsagiRx() {
  var funcs = [];
  return {
    subscribe: function (func) {
      funcs.push(func);
    },
    next: function(state){
      for (var i = 0; i < funcs.length; i++) {
        (function(f, state, i){setTimeout(function(){f(state);}, i)})(funcs[i], state, i);
      }
    }
  }
}

var clickCount = 0;
var CLICK_UP = 0, CLICK_DOWN = 1;

var publishSubject = UsagiRx();
publishSubject.subscribe(countCheck);
publishSubject.subscribe(anotherCountCheck);

$("button").on("click", function(e){
  publishSubject.next(CLICK_UP);
});

setInterval(function() {
  publishSubject.next(CLICK_DOWN);
}, 500);

function countCheck(clickState) {
  if (clickState == CLICK_UP) clickCount++;
  else clickCount--;

  if (clickCount == 5) {
    console.log("Just left five more clicks!");
  }
  if (clickCount <= 0) {
    clickCount = 0;
    console.log("You need to click more quickly");
  }
  if (clickCount == 10) {
    console.log("You clicked 10 times!");
    clickCount = 0;
  }
}

function anotherCountCheck(clickState) {
  if (clickCount == 6) {
    console.log("Just left four more clicks!");
  }
}

13行でRxらしき非同期ライブラリが出来ました。どんなに難しい非同期プログラミングでも、少しづつ改善すればなんとかなるので、皆さんぜひ頑張ってください!

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
3