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

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

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

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

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

```Javascript
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);
```

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

```Javascript
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);
```

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

```Javascript
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がバラバラなので、わけがわからなくなりそうですね。まとめてみましょう。

```Javascript
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を呼ぶようになったらリカーシブになるので、非同期化しましょう。

```Javascript
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が長いんですね。簡単なラッパーを作って、もうちょっと綺麗にしましょう。

```Javascript
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;
  }
}
```

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

```Javascript
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らしき非同期ライブラリが出来ました。どんなに難しい非同期プログラミングでも、少しづつ改善すればなんとかなるので、皆さんぜひ頑張ってください！