非同期プログラミングを支援するイディオムには、
Deferred, Promises, async, await, DOM Promise, Flow.js などがありますが…
今日紹介する Task.js も、非同期プログラミングを支援するライブラリです
( Task.js は Flow.js の改良版です )
Task.js は、Node.js, Browser, WebWorkers 上で動作し、かつ軽量です
環境に依存するコードや、複雑なトリックに依存しない作りになっているため、どこでも動作します
Task.js を導入すると、非同期処理やブラウザのサポート状況に悩まされず、 ロジックのコーディングに集中できます
Task.js はこれら全ての
Needs と Wants を満たしてくれます
では、Task.js の機能を見て行きましょう
function executeUserTask() { return true; }
function callback(err) { console.log("finished"); }
var task = new Task(2, callback);
executeUserTask() ? task.pass() : task.miss(); // sync
setTimeout(function() { // async
executeUserTask() ? task.pass() : task.miss();
}, 1000);
Task.js の基本はこれだけです
次のページからは応用です
Task.js の便利な機能を紹介していきます
使い方 | 該当するAPI |
---|---|
失敗を許す | task.missable() |
データを溜める, 取り出す |
task.buffer(), callback(buffer), Task.flatten(), Task.arraynize(), Task.objectize() |
デバッグする | Task.dump(), Task.drop() |
強制終了する | task.exit() |
エラー | task.message(), task.done() |
もっと待つ | task.extend() |
短く書く | task.done(err) |
Taskを連結する | Junction, Task.run() |
function callback(err) { console.log(err.message); }
var task = new Task(1, callback, { name: "MissableTask" });
task.missable(2);
task.miss(); // ユーザタスク失敗(missableが2なので許容する)
task.miss(); // ユーザタスク失敗(missableが2なので許容する)
task.miss(); // ユーザタスク失敗(missableが2なので待機失敗) -> callback(Error)
function callback(err) { console.log(err.message); }
var task = new Task(1, callback).missable(1);
download(["http://cdn1.example.com/image.png",
"http://cdn2.example.com/image.png"], task);
function download(urls, task) {
var xhr = new XMLHttpRequest();
xhr.onload = function() { task.pass(); };
xhr.onerror = function() {
if ( !task.miss().isFinished() ) {
download(urls, task);
}
};
xhr.open("GET", urls.shift(), true);
xhr.send()
}
function callback(err, buffer) {
console.log(buffer[0]); // -> "value1"
console.log(buffer.key2); // -> "value2"
}
var task = new Task(1, callback);
task.push("value1");
task.set("key2", "value2");
task.pass();
function callback(err, buffer) { // sharedBuffer: ["junction", "value1", "value2"]
console.log(buffer.length); // -> 3
}
var junction = new Task(2, callback).push("junction");
var task1 = new Task(1, junction);
var task2 = new Task(1, junction);
task1.push("value1").pass();
task2.push("value2").pass();
var array = [ [1,2], [3,4] ];
Task.flatten(array); // -> [1, 2, 3, 4]
Task.flatten([ [1,2], [3,4], [ [5,6] ] ]); // -> [1, 2, 3, 4, [5, 6] ]
var array = [1,2,3];
array["key"] = "value"; // Array にプロパティを追加
Task.arraynize(array); // -> [1, 2, 3] になる
var array = [1,2,3];
array["key"] = "value"; // Array にプロパティを追加
Task.objectize(array); // -> { 0: 1, 1: 2, 2: 3, key: "value" }
Task.dump();
{
"anonymous@165": { junction: false, taskCount: 1, missableCount: 0, missedCount: 0, passedCount: 0, state: "" }
"anonymous@166": { junction: false, taskCount: 1, missableCount: 0, missedCount: 0, passedCount: 0, state: "" }
"anonymous@167": { junction: false, taskCount: 1, missableCount: 0, missedCount: 0, passedCount: 0, state: "" }
}
var task = new Task(1, callback, { name: "TEST" });
Task.dump("TEST");
{
"TEST@166": { junction: false, taskCount: 1, missableCount: 0, missedCount: 0, passedCount: 0, state: "" }
}
Task.drop();
function callback(err) { }
var task = new Task(100, callback).missable(100);
task.exit(); // 強制終了 -> callback(new Error(...))
var task = new Task(1, function(err) {
if (err) { console.log(err.message); } // -> "O_o"
});
function userTask(task) {
try {
throw new Error("O_o"); // 例外発生!
task.pass(); // ここには到達しない
} catch (err) {
task.message(err.message).miss(); // task.message("O_o") を設定
}
}
userTask(task);
// このようなありがちなコードが
if (err) { // Error Object
task.message(err.message).miss();
} else {
task.pass();
}
// こうなります
task.done(err);
task.done() を使うと、先ほどのコードも
try {
throw new Error("O_o"); // 例外発生!
task.pass(); // ここには到達しない
} catch (err) {
task.message(err.message).miss(); // task.message("O_o") を設定
}
このように、シンプルになります
try {
throw new Error("O_o"); // 例外発生!
task.pass(); // ここには到達しない
} catch (err) {
task.done(err);
}
function callback(err) { }
var taskCount = 1;
var task = new Task(taskCount, callback);
task.extend(1); // taskCount += 1;
task.pass(); // ユーザタスク成功(taskCount は2なので待機する)
task.pass(); // ユーザタスク成功(taskCount は2なので待機成功で終了する)
// -> callback(null)
function callback(err) {
console.log("finished");
}
var junction = new Task(2, callback);
var task1 = new Task(1, junction);
var task2 = new Task(1, junction);
task1.pass(); // →junction にも状態変化が通知される
task2.pass(); // →junction にも状態変化が通知される
// →junction の待機も終了する
function callback(err) {
console.log("finished");
}
var junction = new Task(2, callback);
var task1 = new Task(1, junction);
var task2 = new Task(1, junction);
task1.pass(); // →junction にも状態変化が通知される
task2.pass(); // →junction にも状態変化が通知される
// →junction の待機も終了する
function callback(err) {
console.log("finished");
}
lv1_junction = new Task(1, callback);
lv2_junction = new Task(1, lv1_junction);
lv3_junction = new Task(2, lv2_junction);
lv4_task1 = new Task(1, lv3_junction);
lv4_task2 = new Task(1, lv3_junction);
lv4_task1.pass();
lv4_task2.pass();
var taskMap = {
a: function(task) { task.pass(); },
b: function(task) { task.pass(); },
c: function(task) { task.pass(); },
d: function(task) { task.pass(); },
};
var junction = new Task(2, callback); // (a > b) + (c + d) が終わったら callback
Task.run("a > b", taskMap, junction); // a を実行後に b を実行
Task.run("c + d", taskMap, junction); // c と d を並列実行
function callback(err, buffer) {
}
Task.run("task_a > task_b + task_c > task_d", {
task_a: function(task) { ... },
task_b: function(task) { ... },
task_c: function(task) { ... },
task_d: function(task) { ... }
}, callback);
>
と +
でつなぐ事で、ユーザタスクの前後間の流れを定義していきます
Task.run("task_a + task_b", {
task_a: function(task) { task.pass(); },
task_b: function(task) { task.pass(); },
}, callback);
+
でつなぐと、それらは同時に実行されますTask.run("task_a > task_b", {
task_a: function(task) { task.pass(); },
task_b: function(task) { task.pass(); },
}, callback);
>
でつなぐと、それらは順番に実行されますTask.run("task_a > 1000 > task_b", {
task_a: function(task) { task.pass(); },
task_b: function(task) { task.pass(); },
}, callback);
function task_a(task) { task.pass(); }
function task_b(task) { task.pass(); }
function task_c(task) { task.pass(); }
// このような直列化したユーザタスクは
Task.run("task_a > task_b > task_c", {
task_a: task_a,
task_b: task_b,
task_c: task_c,
}, callback);
// 配列を使って短く書くことができます
Task.run("", [task_a, task_b, task_c], callback);
Task.run("a > b + c + 1000 > d", {
a: function(task) { task.pass(); },
b: function(task) { task.pass(); },
c: function(task) { task.pass(); },
d: function(task) { task.pass(); }
}, callback);
a > b + c + 1000 > d
は、ユーザタスク a 〜 d を以下の順番で実行します
var argumentForUserTask = { a: 1, b: 2, c : 3, d: 4 };
Task.run("task_a > task_b + task_c > task_d", {
task_a: function(task, arg) { console.log(arg.a); task.pass(); },
/// /////
task_b: function(task, arg) { console.log(arg.b); task.pass(); },
task_c: function(task, arg) { console.log(arg.c); task.pass(); },
task_d: function(task, arg) { console.log(arg.d); task.pass(); },
}, function(err, buffer) {
if (err) {
console.log("ng");
} else {
console.log("ok");
}
}, { arg: argumentForUserTask });
////////////////////////
Task.run("task_a > task_b", {
task_a: function(task) { task.miss(); },
task_b: function(task) { task.pass(); }, // task_b は実行されません
}, callback);
Task.run("task_c + task_d + task_e", {
task_c: function(task) {
setTimeout(function() { task.miss() }, 1000); // 1000ms 後に失敗
},
task_d: function(task) { task.pass(); }, // task_c が中断しても task_d は中断しません
task_e: function(task) { task.pass(); }, // task_c が中断しても task_e は中断しません
}, callback);
Task.run("task_a + task_b + task_c", {
unknown_task_name: function(task) {},
bad_argument: function(/* task */) {}
}, function() {});
> TypeError: Task.run(taskRoute, taskMap)
「非同期のユーザタスク A, B, C, D を、
A, B のグループ と C, D のグループ に分け、
2つのグループの完了を待つ」処理を、
それぞれの方法で実装してみます
function waitForAsyncProcesses(finishedCallback) {
var remainTaskGroupCount1 = [A, B].length; // 2
var remainTaskGroupCount2 = [C, D].length; // 2
var remainJunctionTaskCount = 2;
function A() { setTimeout(function() { doneTaskGroup1(); }, 10); }
function B() { setTimeout(function() { doneTaskGroup1(); }, 100); }
function C() { setTimeout(function() { doneTaskGroup2(); }, 20); }
function D() { setTimeout(function() { doneTaskGroup2(); }, 200); }
function doneTaskGroup1() {
if (--remainTaskGroupCount1 <= 0) { junction(); }
}
function doneTaskGroup2() {
if (--remainTaskGroupCount2 <= 0) { junction(); }
}
function junction() {
if (--remainJunctionTaskCount <= 0) { finishedCallback(); }
}
A(); B(); C(); D();
}
waitForAsyncProcesses(function(err) { console.log("finished"); });
function waitForAsyncProcesses(finishedCallback) {
var promises1 = [A(), B()]; // 2
var promises2 = [C(), D()]; // 2
function A() {
var dfd = jQuery.Deferred();
setTimeout(function() { dfd.resolve(); }, 10);
return dfd.promise();
}
function B() {
var dfd = jQuery.Deferred();
setTimeout(function() { dfd.resolve(); }, 100);
return dfd.promise();
}
function C() {
var dfd = jQuery.Deferred();
setTimeout(function() { dfd.resolve(); }, 20);
return dfd.promise();
}
function D() {
var dfd = jQuery.Deferred();
setTimeout(function() { dfd.resolve(); }, 200);
return dfd.promise();
}
jQuery.when(
jQuery.when.apply(null, promises1), // task group1
jQuery.when.apply(null, promises2) // task group2
).done(function() {
finishedCallback()
});
}
waitForAsyncProcesses(function(err) { console.log("finished"); });
function waitForAsyncProcesses(finishedCallback) {
function A() {
return new Promise(function(resolve, reject) { setTimeout(resolve, 10); });
}
function B() {
return new Promise(function(resolve, reject) { setTimeout(resolve, 100); });
}
function C() {
return new Promise(function(resolve, reject) { setTimeout(resolve, 20); });
}
function D() {
return new Promise(function(resolve, reject) { setTimeout(resolve, 200); });
}
Promise.all([
Promise.all([A(), B()]),
Promise.all([C(), D()])
]).then(function() {
finishedCallback(null);
}).catch(function(err) {
finishedCallback(err);
});
}
waitForAsyncProcesses(function(err) { console.log("finished"); })
function waitForAsyncProcesses(finishedCallback) {
var taskMap = {
A: function(task) { setTimeout(function() { task.pass(); }, 10); },
B: function(task) { setTimeout(function() { task.pass(); }, 100); },
C: function(task) { setTimeout(function() { task.pass(); }, 20); },
D: function(task) { setTimeout(function() { task.pass(); }, 200); },
};
var junction = new Task(2, finishedCallback);
var taskGroup1 = new Task(2, junction);
var taskGroup2 = new Task(2, junction);
taskMap.A(taskGroup1);
taskMap.B(taskGroup1);
taskMap.C(taskGroup2);
taskMap.D(taskGroup2);
}
waitForAsyncProcesses(function(err) { console.log("finished"); });
function waitForAsyncProcesses(finishedCallback) {
var taskMap = {
A: function(task) { setTimeout(function() { task.pass(); }, 10); },
B: function(task) { setTimeout(function() { task.pass(); }, 100); },
C: function(task) { setTimeout(function() { task.pass(); }, 20); },
D: function(task) { setTimeout(function() { task.pass(); }, 200); },
};
var junction = new Task(2, finishedCallback);
Task.run("A + B", taskMap, junction);
Task.run("C + D", taskMap, junction);
}
waitForAsyncProcesses(function(err) { console.log("finished"); });
(ε・◇・)з o O ( スッキリ
https://github.com/uupaa/Task.js
$ npm install uupaa.task.js
var Task = require("uupaa.task.js");
var task = new Task(1, function(err) {
console.log(err ? err.message : "ok");
});
task.pass();
<script src="uupaa.task.js"></script>
<script>
var task = new Task(1, function(err) {
console.log(err ? err.message : "ok");
});
task.pass();
</script>
importScripts("uupaa.task.js");
var task = new Task(1, function(err) {
console.log(err ? err.message : "ok");
});
task.pass();
Open browser console, and try this code.
new Task(1, function() { console.log("Hello Task"); }).pass();
Task.js は以下の特徴を備えています
(ε・◇・)з o O ( Task.js マジ オススメ