Promiseを返す3つの非同期タスク、A
、B
、C
があるとします。それらを一緒に連鎖させたい(つまり、わかりやすくするために、A
によって返された値を取得し、B
を呼び出して)、それぞれについてエラーを正しく処理したい、および最初の失敗で発生します。現在、これを行うには2つの方法があります。
A
.then(passA)
.then(B)
.then(passB)
.then(C)
.then(passC)
.catch(failAll)
ここで、passX
関数は、X
の呼び出しの成功のeachを処理します。しかし、failAll
関数では、A
、B
、C
のエラーのallを処理する必要があります。これらは特に複雑で読みにくい場合があります。 3つの非同期タスクよりも。したがって、他の方法ではこれを考慮します。
A
.then(passA, failA)
.then(B)
.then(passB, failB)
.then(C)
.then(passC, failC)
.catch(failAll)
ここでは、元のfailAll
のロジックをfailA
、failB
、およびfailC
に分離しました。これは、すべてのエラーがソースのすぐ隣で処理されるため、シンプルで読みやすいようです。 しかし、これは私が望むことをしません。
A
が失敗(拒否)した場合、failA
がB
の呼び出しに進まないため、例外をスローするか、rejectを呼び出す必要があります。しかし、これらは両方ともfailB
とfailC
にキャッチされます。つまり、failB
とfailC
は、おそらく状態を維持することによって、すでに失敗したかどうかすなわち変数)。
さらに、非同期タスクが増えると、failAll
関数のサイズが大きくなるか(way 1)、またはfailX
関数がさらに呼び出されるようになります(way 2)。これは私の質問に私をもたらします:
これを行うためのより良い方法はありますか?
考慮事項:then
の例外は拒否メソッドによって処理されるため、Promise.throw
実際にチェーンを切り離す方法?
重複の可能性あり 。ハンドラー内にスコープを追加する回答があります。関数の線形連鎖を尊重し、関数を渡す関数を渡す関数を渡さないという約束はありませんか?
いくつかのオプションがあります。まず、あなたの要件を掘り下げることができるかどうか見てみましょう。
エラーが発生した場所の近くでエラーを処理するため、考えられるすべてのエラーをソートして対処方法を確認する必要がある1つのエラーハンドラーがありません。
1つのプロミスが失敗した場合、チェーンの残りを中止する機能が必要です。
1つの可能性は次のとおりです。
_A().then(passA).catch(failA).then(val => {
return B(val).then(passB).catch(failB);
}).then(val => {
return C(val).then(passC).catch(failC);
}).then(finalVal => {
// chain done successfully here
}).catch(err => {
// some error aborted the chain, may or may not need handling here
// as error may have already been handled by earlier catch
});
_
次に、各failA
、failB
、failC
で、そのステップの特定のエラーを取得します。チェーンを中止する場合は、関数が戻る前に再スローします。チェーンを継続する場合は、通常の値を返すだけです。
上記のコードは、このように書くこともできます(passB
またはpassC
が拒否されたプロミスをスローまたは返す場合の動作は少し異なります)。
_A().then(passA, failA).then(val => {
return B(val).then(passB, failB);
}).then(val => {
return C(val).then(passC, failC);
}).then(finalVal => {
// chain done successfully here
}).catch(err => {
// some error aborted the chain, may or may not need handling here
// as error may have already been handled by earlier catch
});
_
これらは完全に反復的であるため、任意の長さのシーケンスに対してテーブル駆動型にすることもできます。
_function runSequence(data) {
return data.reduce((p, item) => {
return p.then(item[0]).then(item[1]).catch(item[2]);
}, Promise.resolve());
}
let fns = [
[A, passA, failA],
[B, passB, failB],
[C, passC, failC]
];
runSequence(fns).then(finalVal => {
// whole sequence finished
}).catch(err => {
// sequence aborted with an error
});
_
たくさんの約束をつなぐもう一つの便利な点は、リジェクトエラーごとに一意のErrorクラスを作成すると、最後の.catch()
ハンドラーでinstanceof
を使用してエラーの種類を簡単に切り替えることができることです。中断されたチェーンの原因となったステップを知る必要がある場合。 Bluebirdなどのライブラリは、特定のタイプのエラーのみを捕捉する.catch()
を作成するための特定の.catch()
セマンティクスを提供します(try/catchが行う方法など)。 Bluebirdがこれをどのように行うかは、ここで確認できます: http://bluebirdjs.com/docs/api/catch.html 。 (上記の例のように)独自のプロミス拒否で各エラーを処理する場合、最後の.catch()
ステップでエラーの原因となったステップをまだ知る必要がない限り、これは不要です。 。
私がお勧めする2つの方法があります(これで何を達成しようとしているかによって異なります):
はい、Promiseチェーン内のすべてのエラーを1つのキャッチで処理する必要があります。
どれが失敗したかを知る必要がある場合は、次のような一意のメッセージまたは値を使用してプロミスを拒否できます。
A
.then(a => {
if(!pass) return Promise.reject('A failed');
...
})
.then(b => {
if(!pass) return Promise.reject('B failed');
...
})
.catch(err => {
// handle the error
});
または、.then
内で他のプロミスを返すことができます
A
.then(a => {
return B; // B is a different promise
})
.then(b => {
return C; // C is another promise
})
.then(c => {
// all promises were resolved
console.log("Success!")
})
.catch(err => {
// handle the error
handleError(err)
});
これらの約束のそれぞれで、何らかのエラーメッセージが必要になるため、どのエラーが失敗したかがわかります。
そして、これらは矢印関数なので、ブレースを削除できます!約束が大好きなもう一つの理由
A
.then(a => B)
.then(b => C)
.then(c => console.log("Success!"))
.catch(err => handleError(err));
promise-chainの分岐 が可能ですが、正直なところ、特に読みやすさなどのひどい理由で、早期のエラー処理は適切な方法ではありません。同期コードについても同様です。つまり、すべての関数をtry
/catch
しないでください。そうしないと、読みやすくなります。
常にエラーを渡し、肯定的なコードフローが再開する時点でエラーを「処理」します。
物事がどこまで進んだかを知る必要がある場合、私が使用するトリックは単純な進行カウンターです:
let progress = "";
A()
.then(a => (progress = "A passed", passA(a)))
.then(B)
.then(b => (progress = "B passed", passB(b)))
.then(C)
.then(c => (progress = "C passed", passC(c)))
.catch(err => (console.log(progress), failAll(err)))