web-dev-qa-db-ja.com

Promiseチェーンでの複数のキャッチの処理

私はまだ約束にかなり新しく、現在ブルーバードを使用していますが、最善の対処方法がよくわからないシナリオがあります。

たとえば、エクスプレスアプリ内には次のようなプロミスチェーンがあります。

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" });
        });

したがって、私が求めている動作は次のとおりです。

  • Idでアカウントを取得します
  • この時点で拒否された場合は、爆撃してエラーを返します
  • エラーがない場合は、返されたドキュメントをモデルに変換します
  • データベース文書でパスワードを確認します
  • パスワードが一致しない場合、爆撃して別のエラーを返します
  • エラーがなければパスワードを変更してください
  • その後、成功を返します
  • 他に問題が発生した場合は、500を返します

したがって、現在のキャッチはチェーンを停止するようには見えませんが、それは理にかなっているので、エラーに基づいて特定のポイントでチェーンを強制的に停止する方法があるのか​​、それともより良い方法があるのか​​疑問に思っていますif X do Y else Zの場合があるように、これを何らかの形の分岐動作を得るために構造化する。

どんな助けも素晴らしいでしょう。

114
Grofit

この動作は、同期スローとまったく同じです。

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か所にあり、お互いが混雑することはありません。

119

.catchtry-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" });
            }
        });
44
Esailija

エラーに基づいて特定の時点でチェーンを強制的に停止する方法があるかどうか疑問に思っています

いいえ。チェーンの最後までバブルする例外をスローしない限り、チェーンを実際に「終了」することはできません。 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" });
});
16
Bergi

私はこのようにしていました:

あなたは最後にあなたのキャッチを残します。そして、チェーンの途中でエラーが発生した場合にエラーをスローします。

    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);
    }                       
}
3
Leo Leao

おそらくパーティーに少し遅れましたが、ここに示すように.catchをネストすることは可能です。

Mozilla Developer Network-Promiseの使用

編集:これは一般的に尋ねられた機能を提供するため、これを提出しました。ただし、この特定のケースではそうではありません。すでに他の人が詳細に説明したように、.catchはエラーを回復することになっているためです。たとえば、明示的なreturnresolvesのない.catchが原因で、multiple.catchコールバックでクライアントに応答を送信することはできません。その場合はundefinedを使用し、チェーンが実際に解決されていなくても、次の.thenがトリガーされ、後続の.catchがトリガーされ、別の応答がクライアントに送信され、エラーが発生し、おそらくUnhandledPromiseRejection あなたのやり方で。この複雑な文があなたにとって何らかの意味をなしたことを願っています。

1
denkquer

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を返します。

0

.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)は、最新のエラーと対話するためのちょっとしたハックです。

0
mvndaai