メインコンテンツへ移動

Promise の適用シナリオ

無料2015-11-21#JS#promise的用例#javascript promise use case#Promise怎么用#promise有什么用#promise实例#任务队列#任务管道

Promise にはどのような用途がありますか?どのようなシナリオに適していますか?

一. シリアルタスクキューの実装

タスクキューとは、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));
});

コメント

コメントはまだありません

コメントを書く