一. シリアルタスクキューの実装
タスクキューとは、A が完了したら B を呼び出し、B が完了したら C を呼び出すことを指します(ここでいう ABC はすべて非同期操作を表します)... 以前の做法はコールバックをネストすることで、コードは以下のようになっていまし��:
taskA(data1, function(res) {
if (res.state === OK) {
taskB(data2, function(res) {
if (res.state === OK) {
taskC(data3, function(res) {
if (res.state === OK) {
// ...
}
else {
// err3
}
});
}
else {
// err2
}
});
}
else {
// err1
}
});
各ステップでエラーが発生する可能性があり(err1, err2, err3...)、エラー処理も異なる可能性があります。コード構造は非常に膨れ上がり(制御不能に「横方向に発展」し、最終的に「一塊」のコードになる)、非常に醜くなります。各ステップが非同期 + コールバックであるため、各 callback を分離するのが非常に困難です。
Promise の最も顕著な効果はこの「コールバックピラミッド」を解消することであり、コードはもはや横方向に発展しません:
// taskX はすべて Promise オブジェクトを返す
taskA(data1).then(
taskB(data2),
errHandler1
).then(
taskC(data3),
errHandler2
).then(
displayData,
errHandler3
);
taskA と taskB のエラー処理が同じであれば、さらに簡素化できます:
// taskX はすべて Promise オブジェクトを返す
taskA(data1).then(taskB(data2)).then(
taskC(data3),
errHandler12
).then(
displayData,
errHandler3
);
エラーを統一して処理したい場合は、このようにできます:
// taskX はすべて Promise オブジェクトを返す
taskA(data1).then(taskB(data2)).then(taskC(data3)).then(displayData).catch(errHandler);
// 以下と同等
taskA(data1).then(taskB(data2)).then(taskC(data3)).then(displayData, errHandler);
チェーン呼び出し + エラーの自動後方スローにより、書くのも見るのも良好です
二. シリアルタスクパイプラインの実装
タスクパイプラインとは、現在のタスクの出力を次のタスクの入力として使用でき、データパイプラインを形成することを指します。jQuery のコードは以下のようになります:
$.post(url1, data, function(res) {
if (res.state === OK) {
$.post(url2, res.data, function(res) {
if (res.state === OK) {
$.post(url3, res.data, function(res) {
if (res.state === OK) {
// ...
}
else {
// err3
}
});
}
else {
// err2
}
});
}
else {
// err1
}
});
実際のシナリオでは非常に一般的です。例えば、url1 からパラメータ userId を取得し、それを拿到して url2 からサードパーティの openId を取得し、最後に url3 から orderList を取得して、結果をユーザーに展示します。このようなロジックはすべて自然なタスクパイプラインです。
Promise を使用すると、このように実装できます:
new Promise(function(resolve, reject) {
resolve(1);
}).then(function(res) {
return new Promise(function(resolve, reject) {
resolve(res + 1);
});
}).then(function(res) {
return new Promise(function(resolve, reject) {
resolve(res + 1);
});
}).then(function(res) {
console.log(res);
});
// => 3
P.S. jQuery の $.when() は一種の Promise 実装ですが、Promise A+ 仕様とは若干の違いがあります。詳細は jQuery deffered と promise オブジェクトメソッド をご覧ください。
三. 実行時でなければ実行順序が確定しないシリアルタスクの処理
A、B、C の 3 つのタスクがあると仮定します。実行順序は不確定で、BCA、BAC などの可能性があります。実行時になって初めて 3 者の実行順序が確定しますが、順序が確定すれば直ちにシリアル実行する必要があります。
この場合、コールバックのネストではもはや対応できません。なぜなら、各順序に対応するために別のコールバックネストが必要になるからです。私たちはコールバックを自由に組み合わせられるメカニズムが必要です。例えば Promise のような:
var tasks = getOrderedTasks();
var p = tasks[0];
tasks.forEach(function(item, index) {
p.then(item);
});
このように書くと副作用としてパイプラインがなくなります(タスクの出力を次のタスクの入力として渡せなくなる)が、問題ありません。簡単に修正できます:
function catchFuture(future) {
// future を処理/渡す
// ...
console.log('catchFuture: ' + future);
}
var tasks = getOrderedTasks();
var p = tasks[0];
tasks.forEach(function(item, index) {
// p.then(item);
p.then(item.then(catchFuture));
});
コメントはまだありません