Promiseを拒否するのはエラーの場合のみですか?


25

約束を返すこの関数を認証させるとしましょう。その結果、約束が解決します。私が見るように、falseとtrueは期待される結果であり、拒否はエラーの場合にのみ発生するはずです。または、認証の失敗は、約束を拒否する何かと見なされますか?


認証に失敗した場合はrejectfalseを返す必要がありますが、値がa Boolであると予想される場合成功し、値に関係なくBoolで解決する必要があります。Promiseは値の一種のプロキシです-返された値を保存するため、値を取得できなかった場合にのみを使用してくださいreject。それ以外の場合はする必要がありresolveます。

これはいい質問です。約束の設計の失敗の1つに触れます。エラーには、ユーザーが不適切な入力を提供したときなどの予期されるエラー(ログインの失敗など)と、コードのバグである予期しないエラーの2種類があります。promiseの設計では、2つの概念が1つのフローに統合されているため、2つの概念を区別して処理することは困難です。
zzzzBov

1
私がいることを言うと思います解決手段は、応答を使用してアプリケーションを続け、一方で拒絶(再試行するか、他の何かを行う可能性と)現在の操作をキャンセルする手段を。

4
それについて考える別の方法-これが同期メソッド呼び出しである場合、通常の認証失敗(悪いユーザー名/パスワード)を返すfalseか、例外をスローするように扱いますか?
wrschneider

2
取得APIは、この良い例です。thenエラーコードが返された場合でも、サーバーが応答すると常にトリガーされますresponse.okcatchハンドラはのみのためにトリガされ、予期しないエラー。
CodingIntrigue

回答:


22

良い質問!難しい答えはありません。それはあなたがフローのその特定のポイントで例外的あると考えるものによります。

aを拒否することPromiseは、例外を発生させることと同じです。すべての望ましくない結果が例外であるというわけではなくエラーの結果です。あなたは両方の方法であなたのケースを主張することができます:

  1. 失敗した認証べき、呼び出し側が期待しているので、お返しにオブジェクトを、そして何かがこのフローの例外です。rejectPromiseUser

  2. 間違った資格情報を提供することは実際には例外的なケースではないため、認証に失敗しresolvePromiseとしても、呼び出し側はフローが常にになることを期待すべきではありません。nullUser

私は問題を発信者側から見ていることに注意してください。情報の流れの中で、呼び出し元自分のアクションが結果をもたらすと期待Userていますか(それ以外はエラーです)、この特定の呼び出し元が他の結果を処理するのは理にかなっていますか?

多層システムでは、データが層を通過するにつれて答えが変わる場合があります。例えば:

  • HTTPレイヤーはRESOLVE!と言います。要求が送信され、ソケットが完全に閉じられ、サーバーが有効な応答を送信しました。取得APIはこれを行います。
  • プロトコル層はそれから拒否します!応答のステータスコードは401で、HTTPでは問題ありませんが、プロトコルでは問題ありません!
  • 認証層はNO、RESOLVEと言います!401は間違ったパスワードの予想されるステータスであり、nullユーザーに解決されるため、エラーをキャッチします。
  • インターフェイスコントローラーが「どれも拒否します!」画面に表示されるモーダルは、ユーザー名とアバターを予期していましたが、この情報以外の情報はこの時点ではエラーです。

この4点の例は明らかに複雑ですが、2点を示しています。

  1. 何かが例外/拒否であるかどうかは、周囲の流れと期待に依存します
  2. プログラムの異なる層は、フローのさまざまな段階にあるため、同じ結果を異なる方法で処理する場合があります

繰り返しますが、難しい答えはありません。考えて設計する時間です!


6

したがって、Promiseには関数型言語からJSをもたらすという優れた特性があります。つまり、ロジックに1つのブランチまたは他のブランチのいずれかを強制することによりEitherLeftタイプとタイプという2つの他のタイプを結合するこのタイプコンストラクターを実際に実装Rightしますブランチ。

data Either x y = Left x | Right y

さて、あなたは実際、左側の型が約束に対して曖昧であることに気づいています。何でも拒否できます。これは、JSの型指定が弱いため当てはまりますが、防御的なプログラミングを行う場合は注意が必要です。

その理由は、JSがthrowプロミス処理コードからステートメントを取得し、そのLeftサイドにバンドルするからです。技術的にJSであなたのことができthrow、真/の虚偽または文字列または数字を含め、すべてのものを:しかし、JavaScriptコードもせずに物事をスローthrow(あなたはヌルにアクセスプロパティしようとしているようなことを行うとき)、このための定住API(そこにあるErrorオブジェクト) 。したがって、キャッチに取り掛かるとき、通常、それらのエラーがErrorオブジェクトであると想定できると便利です。またreject、上記のバグのいずれかのエラーでプロミスが凝集するため、通常は、throw他のエラーのみを使用して、catch文にシンプルで一貫したロジックを持たせます。

したがって、if-conditionalをに入れて、誤ったエラーを探すことができますcatch、その場合、真実のケースは簡単です。

Either (Either Error ()) ()

少なくともオーセンティケーターからすぐに出てくるものについては、より単純なブール値の論理構造を好むでしょう。

Either Error Bool

実際、次のレベルの認証ロジックはおそらくUser、認証されたユーザーを含む何らかのオブジェクトを返すことになるため、次のようになります。

Either Error (Maybe User)

そして、これは多かれ少なかれ私が期待するものです:nullユーザーが定義されていない場合に戻り、そうでなければreturn {user_id: <number>, permission_to_launch_missiles: <boolean>}。ログインしていないという一般的なケースは、たとえば「新しいクライアントへのデモ」モードの場合など、救助可能であり、誤ってobject.doStuff()いつobject.doStuffに呼び出されたバグと混同しないでくださいundefined

さて、それで、あなたがしたいことはから派生するNotLoggedInor PermissionError例外を定義することですError。次に、本当にそれを必要とするものであなたが書きたい:

function launchMissiles() {
    function actuallyLaunchThem() {
        // stub
    }
    return getAuth().then(auth => {
        if (auth === null) {
            throw new PermissionError('Cannot launch missiles without permission, cannot have permission if not logged in.');
        } else if (auth.permission_to_launch_missiles) {
            return actuallyLaunchThem();
        } else {
            throw new PermissionError(`User ${auth.user_id} does not have permission to launch the missiles.`);
        }
    });
}

3

エラー

エラーについて話しましょう。

エラーには2つのタイプがあります。

  • 予想されるエラー
  • 予期しないエラー
  • オフバイワンエラー

予想されるエラー

予期されるエラーとは、間違ったことが発生する状態ですが、その可能性があることはわかっているため、対処します。

これらは、ユーザー入力やサーバー要求などです。ユーザーが間違いを犯したり、サーバーがダウンしたりする可能性があることを知っているので、チェックコードを記述して、プログラムが入力を再度要求するか、メッセージを表示するか、その他の適切な動作を確認します。

これらは処理されると回復可能です。未処理のままにすると、予期しないエラーになります。

予期しないエラー

予期しないエラー(バグ)は、コードが間違っているために間違ったことが起こる状態です。それらは最終的には発生することを知っていますが、定義により予想外であるため、どこでどのように対処するかを知る方法はありません。

これらは、構文エラーや論理エラーなどです。コードにタイプミスがあり、間違ったパラメーターで関数を呼び出した可能性があります。通常、これらは回復できません。

try..catch

について話しましょうtry..catch

JavaScriptでは、throw一般的に使用されません。コード内の例を見てみると、それらはほとんどないでしょうし、通常は

function example(param) {
  if (!Array.isArray(param) {
    throw new TypeError('"param" should be an array!');
  }
  ...
}

このため、try..catch制御フローではブロックもそれほど一般的ではありません。通常、メソッドを呼び出す前にいくつかのチェックを追加して、予期されるエラーを回避するのは非常に簡単です。

JavaScript環境もかなり寛容なので、予期しないエラーもしばしばキャッチされずに残されます。

try..catch珍しいことはありません。JavaやC#などの言語ではより一般的な、いくつかの便利な使用例があります。JavaとC#には型付きcatch構造の利点があるため、予想されるエラーと予期しないエラーを区別できます。

C#
try
{
  var example = DoSomething();
}
catch (ExpectedException e)
{
  DoSomethingElse(e);
}

この例では、他の予期しない例外を上に流して、他の場所で処理できます(ログに記録してプログラムを閉じるなど)。

JavaScriptでは、この構成は次の方法で複製できます。

try {
  let example = doSomething();
} catch (e) {
  if (e instanceOf ExpectedError) {
    DoSomethingElse(e);
  } else {
    throw e;
  }
}

それほどエレガントではありませんが、これは珍しい理由の一部です。

関数

関数について話しましょう。

単一の責任原則を使用する場合、各クラスと関数は単一の目的を果たす必要があります。

たとえばauthenticate()、ユーザーを認証する場合があります。

これは次のように記述できます。

const user = authenticate();
if (user == null) {
  // keep doing stuff
} else {
  // handle expected error
}

または、次のように記述される場合があります。

try {
  const user = authenticate();
  // keep doing stuff
} catch (e) {
  if (e instanceOf AuthenticationError) {
    // handle expected error
  } else {
    throw e;
  }
}

どちらも受け入れられます。

約束

約束について話しましょう。

Promiseはの非同期形式ですtry..catch。コードを呼び出すnew PromisePromise.resolvetryコードを開始します。呼び出すthrowPromise.rejectcatchコードを送信します。

Promise.resolve(value)   // try
  .then(doSomething)     // try
  .then(doSomethingElse) // try
  .catch(handleError)    // catch

ユーザーを認証する非同期関数がある場合、次のように記述できます。

authenticate()
  .then((user) => {
    if (user == null) {
      // keep doing stuff
    } else {
      // handle expected error
    }
  });

または、次のように記述される場合があります。

authenticate()
  .then((user) => {
    // keep doing stuff
  })
  .catch((e) => {
    if (e instanceOf AuthenticationError) {
      // handle expected error
    } else {
      throw e;
    }
  });

どちらも受け入れられます。

ネスティング

ネスティングについて話しましょう。

try..catchネストできます。あなたのauthenticate()方法は、内部的に持っているかもしれないtry..catch次のようなブロックを

try {
  const credentials = requestCredentialsFromUser();
  const user = getUserFromServer(credentials);
} catch (e) {
  if (e instanceOf CredentialsError) {
    // handle failure to request credentials
  } else if (e instanceOf ServerError) {
    // handle failure to get data from server
  } else {
    throw e; // no idea what happened
  }
}

同様に、promiseはネストできます。非同期authenticate()メソッドは、内部的にプロミスを使用する場合があります。

requestCredentialsFromUser()
  .then(getUserFromServer)
  .catch((e) => {
    if (e instanceOf CredentialsError) {
      // handle failure to request credentials
    } else if (e instanceOf ServerError) {
      // handle failure to get data from server
    } else {
      throw e; // no idea what happened
    }
  });

答えは何ですか?

さて、質問に実際に答える時が来たと思います。

認証の失敗は、約束を拒否する何かと考えられますか?

私ができる最も簡単な答えは、throw同期コードである場合は例外を避けたいと思うところならどこでも約束を拒否すべきだということです。

ステートメントにいくつifかのチェックをthen入れることで制御フローがより簡単になった場合、約束を拒否する必要はありません。

約束を拒否し、エラー処理コードでエラーのタイプをチェックすることで制御フローがより単純な場合は、代わりにそれを実行します。


0

Promiseの「拒否」ブランチを使用して、jQuery UIダイアログボックスの「キャンセル」アクションを表しました。特に、ダイアログボックスに複数の「閉じる」オプションがあることが多いため、「解決」ブランチを使用するよりも自然に見えました。


私が知っているほとんどの純粋主義者はあなたに同意しないだろう。

0

約束の処理は、「if」条件に似ています。認証が失敗した場合に「解決」するか「拒否」するかは、あなた次第です。


1
約束は非同期であるtry..catch、ではありませんif
zzzzBov 16

@zzzBoxそのロジックによって、Promiseを非同期として使用し、try...catch完了して結果を得ることができた場合は、受け取った値に関係なく解決する必要があると単純に言う必要があります。そうでなければ拒否する必要がありますか?

@somethinghere、いいえ、あなたは私の議論を誤解しています。try { if (!doSomething()) throw whatever; doSomethingElse() } catch { ... }まったく問題ありませんPromiseが、aが表す構造はtry..catch部分であり、部分ではありませんif
zzzzBov 16

@zzzzBov私はすべて公平にそれを得ました:)私はアナロジーが好きです。しかし、私のロジックは、doSomething()失敗した場合はスローするという単純なものですが、そうでない場合は必要な値が含まれている可能性があります(ここでのアイデアの一部ではないためif上記は少し混乱してます:))。テストが失敗した場合、スローする理由がある場合(類推で)のみ拒否する必要があります。テストが成功した場合、その値が正であるかどうかに関係なく、常に解決する必要がありますよね?

コメントは私の考えを表現するのに十分ではないので、@ somethinghere、私は答えを書き上げることにしました(これは十分に長く開いていると仮定します)。
zzzzBov 16
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.