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