私はまだ約束にかなり新しく、現在ブルーバードを使用していますが、最善の対処方法がよくわからないシナリオがあります。
たとえば、エクスプレスアプリ内には次のようなプロミスチェーンがあります。
repository.Query(getAccountByIdQuery)
.catch(function(error){
res.status(404).send({ error: "No account found with this Id" });
})
.then(convertDocumentToModel)
.then(verifyOldPassword)
.catch(function(error) {
res.status(406).send({ OldPassword: error });
})
.then(changePassword)
.then(function(){
res.status(200).send();
})
.catch(function(error){
console.log(error);
res.status(500).send({ error: "Unable to change password" });
});
したがって、私が求めている動作は次のとおりです。
したがって、現在のキャッチはチェーンを停止するようには見えませんが、それは理にかなっているので、エラーに基づいて特定のポイントでチェーンを強制的に停止する方法があるのか、それともより良い方法があるのか疑問に思っていますif X do Y else Z
の場合があるように、これを何らかの形の分岐動作を得るために構造化する。
どんな助けも素晴らしいでしょう。
この動作は、同期スローとまったく同じです。
try{
throw new Error();
} catch(e){
// handle
}
// this code will run, since you recovered from the error!
これは.catch
のポイントの半分です-エラーから回復できるようにするため。状態がまだエラーであることを示すために再スローすることが望ましい場合があります。
try{
throw new Error();
} catch(e){
// handle
throw e; // or a wrapper over e so we know it wasn't handled
}
// this code will not run
ただし、エラーは後のハンドラでキャッチされるため、これだけでは機能しません。ここでの本当の問題は、一般化された「HANDLE ANYTHING」エラーハンドラーは一般に悪い習慣であり、他のプログラミング言語やエコシステムで非常に嫌われていることです。このため、Bluebirdは型付きの述語キャッチを提供しています。
追加された利点は、ビジネスロジックが要求/応答サイクルをまったく意識する必要がないことです(そうする必要はありません)。クライアントが取得するHTTPステータスとエラーを決定するのはクエリの責任ではなく、アプリが成長するにつれて、クライアントに送信するものからビジネスロジック(DBのクエリ方法とデータの処理方法)を分離することができます(httpステータスコード、テキスト、応答)。
コードを作成する方法は次のとおりです。
最初に、.Query
を取得してNoSuchAccountError
をスローし、Bluebirdが既に提供しているPromise.OperationalError
からサブクラス化します。エラーをサブクラス化する方法がわからない場合はお知らせください。
AuthenticationError
のサブクラスを追加して、次のようなことをします。
function changePassword(queryDataEtc){
return repository.Query(getAccountByIdQuery)
.then(convertDocumentToModel)
.then(verifyOldPassword)
.then(changePassword);
}
あなたが見ることができるように-それは非常にきれいであり、あなたはプロセスで何が起こるかの取扱説明書のようなテキストを読むことができます。また、要求/応答から分離されています。
これで、ルートハンドラーから次のように呼び出します。
changePassword(params)
.catch(NoSuchAccountError, function(e){
res.status(404).send({ error: "No account found with this Id" });
}).catch(AuthenticationError, function(e){
res.status(406).send({ OldPassword: error });
}).error(function(e){ // catches any remaining operational errors
res.status(500).send({ error: "Unable to change password" });
}).catch(function(e){
res.status(500).send({ error: "Unknown internal server error" });
});
このように、ロジックはすべて1か所にあり、クライアントへのエラーの処理方法の決定はすべて1か所にあり、お互いが混雑することはありません。
.catch
はtry-catch
ステートメントと同様に機能します。つまり、最後に1つのキャッチだけが必要です。
repository.Query(getAccountByIdQuery)
.then(convertDocumentToModel)
.then(verifyOldPassword)
.then(changePassword)
.then(function(){
res.status(200).send();
})
.catch(function(error) {
if (/*see if error is not found error*/) {
res.status(404).send({ error: "No account found with this Id" });
} else if (/*see if error is verification error*/) {
res.status(406).send({ OldPassword: error });
} else {
console.log(error);
res.status(500).send({ error: "Unable to change password" });
}
});
エラーに基づいて特定の時点でチェーンを強制的に停止する方法があるかどうか疑問に思っています
いいえ。チェーンの最後までバブルする例外をスローしない限り、チェーンを実際に「終了」することはできません。 Benjamin Gruenbaum's answer を参照してください。
彼のパターンの派生は、エラータイプを区別することではなく、単一の汎用.catch
ハンドラーから送信できるstatusCode
およびbody
フィールドを持つエラーを使用することです。ただし、アプリケーション構造に応じて、彼のソリューションはよりクリーンになる場合があります。
または、これを構造化して、何らかの形の分岐動作を取得するより良い方法がある場合
はい、できます promiseによる分岐 。ただし、これは、ネストされたif-elseまたはtry-catchステートメントで行うように、チェーンを離れてネストに「戻る」ことを意味します。
repository.Query(getAccountByIdQuery)
.then(function(account) {
return convertDocumentToModel(account)
.then(verifyOldPassword)
.then(function(verification) {
return changePassword(verification)
.then(function() {
res.status(200).send();
})
}, function(verificationError) {
res.status(406).send({ OldPassword: error });
})
}, function(accountError){
res.status(404).send({ error: "No account found with this Id" });
})
.catch(function(error){
console.log(error);
res.status(500).send({ error: "Unable to change password" });
});
私はこのようにしていました:
あなたは最後にあなたのキャッチを残します。そして、チェーンの途中でエラーが発生した場合にエラーをスローします。
repository.Query(getAccountByIdQuery)
.then((resultOfQuery) => convertDocumentToModel(resultOfQuery)) //inside convertDocumentToModel() you check for empty and then throw new Error('no_account')
.then((model) => verifyOldPassword(model)) //inside convertDocumentToModel() you check for empty and then throw new Error('no_account')
.then(changePassword)
.then(function(){
res.status(200).send();
})
.catch((error) => {
if (error.name === 'no_account'){
res.status(404).send({ error: "No account found with this Id" });
} else if (error.name === 'wrong_old_password'){
res.status(406).send({ OldPassword: error });
} else {
res.status(500).send({ error: "Unable to change password" });
}
});
他の関数はおそらく次のようになります。
function convertDocumentToModel(resultOfQuery) {
if (!resultOfQuery){
throw new Error('no_account');
} else {
return new Promise(function(resolve) {
//do stuff then resolve
resolve(model);
}
}
おそらくパーティーに少し遅れましたが、ここに示すように.catch
をネストすることは可能です。
Mozilla Developer Network-Promiseの使用
編集:これは一般的に尋ねられた機能を提供するため、これを提出しました。ただし、この特定のケースではそうではありません。すでに他の人が詳細に説明したように、.catch
はエラーを回復することになっているためです。たとえば、明示的なreturn
resolvesのない.catch
が原因で、multiple.catch
コールバックでクライアントに応答を送信することはできません。その場合はundefined
を使用し、チェーンが実際に解決されていなくても、次の.then
がトリガーされ、後続の.catch
がトリガーされ、別の応答がクライアントに送信され、エラーが発生し、おそらくUnhandledPromiseRejection
あなたのやり方で。この複雑な文があなたにとって何らかの意味をなしたことを願っています。
Benjamin Gruenbaumの上記の答え は複雑な論理シーケンスの最良の解決策であると思いますが、より簡単な状況のための私の代替案があります。 errorEncountered
フラグをreturn Promise.reject()
と共に使用して、後続のthen
またはcatch
ステートメントをスキップします。したがって、次のようになります。
let errorEncountered = false;
someCall({
/* do stuff */
})
.catch({
/* handle error from someCall*/
errorEncountered = true;
return Promise.reject();
})
.then({
/* do other stuff */
/* this is skipped if the preceding catch was triggered, due to Promise.reject */
})
.catch({
if (errorEncountered) {
return;
}
/* handle error from preceding then, if it was executed */
/* if the preceding catch was executed, this is skipped due to the errorEncountered flag */
});
2つ以上のthen/catchペアがある場合は、おそらくBenjamin Gruenbaumのソリューションを使用する必要があります。しかし、これは簡単なセットアップでは機能します。
最後のcatch
にはreturn Promise.reject();
ではなくreturn;
しかありません。これは、スキップする必要がある後続のthen
がなく、Nodeが好まない未処理のPromise拒否としてカウントされるためです。上記のように、最後のcatch
は平和的に解決されたPromiseを返します。
.then().catch()...
の代わりに.then(resolveFunc, rejectFunc)
を実行できます。このプロミスチェーンは、物事を途中で処理する方が良いでしょう。書き直す方法は次のとおりです。
repository.Query(getAccountByIdQuery)
.then(
convertDocumentToModel,
() => {
res.status(404).send({ error: "No account found with this Id" });
return Promise.reject(null)
}
)
.then(
verifyOldPassword,
() => Promise.reject(null)
)
.then(
changePassword,
(error) => {
if (error != null) {
res.status(406).send({ OldPassword: error });
}
return Promise.Promise.reject(null);
}
)
.then(
_ => res.status(200).send(),
error => {
if (error != null) {
console.error(error);
res.status(500).send({ error: "Unable to change password" });
}
}
);
注:if (error != null)
は、最新のエラーと対話するためのちょっとしたハックです。