JavaScriptの約束-拒否とスロー


385

私はこの問題に関するいくつかの記事を読みましたがPromise.reject、エラーをスローすることとそれをスローすることの間に違いがあるかどうかはまだはっきりしません。例えば、

Promise.rejectの使用

return asyncIsPermitted()
    .then(function(result) {
        if (result === true) {
            return true;
        }
        else {
            return Promise.reject(new PermissionDenied());
        }
    });

スローを使用する

return asyncIsPermitted()
    .then(function(result) {
        if (result === true) {
            return true;
        }
        else {
            throw new PermissionDenied();
        }
    });

私の好みはthrow、それが短いからといって単純に使用することですが、一方が他方よりも優れているかどうか疑問に思っていました。


9
どちらの方法でも、まったく同じ応答が得られます。.then()ハンドラがスローされた例外をキャッチし、自動的に拒否約束に変換します。スローされた例外の実行は特に高速ではないことを読んだので、拒否されたプロミスを返す方が実行が少し速いかもしれないと思いますが、それを知ることが重要である場合、複数の最新のブラウザーでテストを考案する必要があります。throw読みやすさが好きなので個人的に使用しています。
jfriend00

@webduvetにはPromiseはありません-それらはスローで動作するように設計されています。
ジョーズ、2015年

15
欠点の1つthrowは、setTimeoutなどの非同期コールバック内からプロミスがスローされても、プロミスが拒否されないことです。jsfiddle.net/m07van33 @Blondie正解です。
ケビンB

@joewsそれは良いことではありません;)
webduvet '30 / 10/30

1
ああ、そうだ。したがって、私のコメントの明確化は、約束されなかった非同期コールバック内からスローされた場合」です。例外があることは知っていましたが、それが何なのか思い出せませんでした。私もスローを使用することを好むのは、それがより読みやすくなりreject、パラメーターリストから省略できるからです。
ケビンB

回答:


346

どちらか一方を使用する利点はありませんが、機能しthrowない特定のケースがあります。ただし、これらのケースは修正できます。

promiseコールバック内にいるときはいつでも、を使用できますthrow。ただし、他の非同期コールバックを使用してrejectいる場合は、を使用する必要があります。

たとえば、これはキャッチをトリガーしません:

new Promise(function() {
  setTimeout(function() {
    throw 'or nah';
    // return Promise.reject('or nah'); also won't work
  }, 1000);
}).catch(function(e) {
  console.log(e); // doesn't happen
});

代わりに、未解決の約束とキャッチされていない例外が残ります。代わりにを使用したい場合ですreject。ただし、これは2つの方法で修正できます。

  1. タイムアウト内で元のPromiseの拒否機能を使用します。

new Promise(function(resolve, reject) {
  setTimeout(function() {
    reject('or nah');
  }, 1000);
}).catch(function(e) {
  console.log(e); // works!
});

  1. タイムアウトを約束することにより:

function timeout(duration) { // Thanks joews
  return new Promise(function(resolve) {
    setTimeout(resolve, duration);
  });
}

timeout(1000).then(function() {
  throw 'worky!';
  // return Promise.reject('worky'); also works
}).catch(function(e) {
  console.log(e); // 'worky!'
});


54
約束されていない非同期コールバック内の使用できない場所に言及する価値があることは、OPが比較するように求めていたものthrow errorも使用できないことですreturn Promise.reject(err)。これが基本的に、Promise内に非同期コールバックを配置してはならない理由です。非同期のすべてを約束してから、これらの制限はありません。
jfriend00

9
「ただし、他の種類のコールバックを使用している場合」は、実際には「他の種類の非同期コールバックを使用している場合」である必要があります。コールバックは(たとえばとArray#forEach)同期することができ、コールバックは内部でスローすることで機能します。
フェリックスSaparelli

2
@KevinBがこれらの行を読み取ると、「スローが機能しない特定のケースがあります」。「promiseコールバックの内部にいるときはいつでも、throwを使用できます。ただし、他の非同期コールバックにいる場合は、rejectを使用する必要があります。」サンプルスニペットthrowは機能しないケースを示しているのではなくPromise.reject、より良い選択であると感じます。ただし、スニペットはこれら2つの選択肢のいずれの影響も受けず、何を選択しても同じ結果になります。何か不足していますか?
Anshul 2017年

2
はい。setTimeoutでthrowを使用すると、catchは呼び出されません。コールバックrejectに渡されたを使用する必要がありnew Promise(fn)ます。
ケビンB

2
@KevinB一緒にいてくれてありがとう。OPの例では、とを具体的に比較return Promise.reject()したいと述べていますthrow。彼は構成でreject与えられたコールバックについては触れていませんnew Promise(function(resolve, reject))。したがって、2つのスニペットがresolveコールバックをいつ使用すべきかを正しく示していますが、OPの質問はそれではありませんでした。
Anshul 2017

202

もう一つの重要な事実はということですreject() DOESはしないように制御フローを終了するreturn文はありません。対照的に、throw制御フローは終了します。

例:

new Promise((resolve, reject) => {
  throw "err";
  console.log("NEVER REACHED");
})
.then(() => console.log("RESOLVED"))
.catch(() => console.log("REJECTED"));

new Promise((resolve, reject) => {
  reject(); // resolve() behaves similarly
  console.log("ALWAYS REACHED"); // "REJECTED" will print AFTER this
})
.then(() => console.log("RESOLVED"))
.catch(() => console.log("REJECTED"));


51
ポイントは正しいですが、比較はトリッキーです。なぜなら、通常はと書いて拒否されたプロミスを返す必要があるreturn reject()ため、次の行は実行されません。
AZ。

7
なぜ返品したいですか?
lukyer 2017年

31
この場合、return reject()は単に省略形です。reject(); returnつまり、必要なのはフローを終了することです。executor(に渡される関数new Promise)の戻り値は使用されないため、安全です。
フェリックスSaparelli

47

はい、最大の違いは、rejectはpromiseが拒否された後に実行されるコールバック関数ですが、throwは非同期で使用できないことです。rejectを使用することを選択した場合、コードは通常どおり非同期で実行されますが、throwはリゾルバー関数の完了を優先します(この関数はすぐに実行されます)。

私が問題を明確にするのを助けた私が見た例は、あなたが拒否でタイムアウト機能を設定することができたということでした、例えば:

new Promise(_, reject) {
 setTimeout(reject, 3000);
});

上記はスローで書くことはできませんでした。

あなたの小さな例では、区別がつかないほどの違いですが、より複雑な非同期の概念を扱う場合、2つの違いは劇的なものになる可能性があります。


1
これは重要な概念のように聞こえますが、書かれているとおりに理解できません。Promisesはまだ新しすぎると思います。
David Spector

43

TLDR: 関数がプロミスを返したり、例外をスローしたりする場合、関数は使いにくいです。非同期関数を作成するとき、拒否されたpromiseを返すことで失敗を通知することを好む

あなたの特定の例はそれらの間のいくつかの重要な違いを難読化しています:

あなたはプロミスチェーン内でエラー処理をしているため、スローされた例外は自動的に拒否されたプロミスに変換されます。これは、それらが交換可能であるように見える理由を説明するかもしれません-それらは交換可能ではありません。

以下の状況を考慮してください。

checkCredentials = () => {
    let idToken = localStorage.getItem('some token');
    if ( idToken ) {
      return fetch(`https://someValidateEndpoint`, {
        headers: {
          Authorization: `Bearer ${idToken}`
        }
      })
    } else {
      throw new Error('No Token Found In Local Storage')
    }
  }

これは、非同期と同期の両方のエラーケースをサポートする必要があるため、アンチパターンになります。次のようになります。

try {
  function onFulfilled() { ... do the rest of your logic }
  function onRejected() { // handle async failure - like network timeout }
  checkCredentials(x).then(onFulfilled, onRejected);
} catch (e) {
  // Error('No Token Found In Local Storage')
  // handle synchronous failure
} 

良くありません。ここPromise.reject(グローバルスコープで利用可能)が救いに出て、効果的に自分自身を区別する場所throwです。リファクタリングは次のようになります。

checkCredentials = () => {
  let idToken = localStorage.getItem('some_token');
  if (!idToken) {
    return Promise.reject('No Token Found In Local Storage')
  }
  return fetch(`https://someValidateEndpoint`, {
    headers: {
      Authorization: `Bearer ${idToken}`
    }
  })
}

これによりcatch()、ネットワーク障害トークン不足の同期エラーチェックに1つだけ使用できるようになりました。

checkCredentials()
      .catch((error) => if ( error == 'No Token' ) {
      // do no token modal
      } else if ( error === 400 ) {
      // do not authorized modal. etc.
      }

1
ただし、Opの例では常にpromiseが返されます。問題は、拒否されたプロミス(次にジャンプするプロミス)を使用するかどうか、Promise.rejectまたはthrowいつ返すかを指定することです.catch()
マルコスペレイラ

@maxwell-私はあなたの例が好きです。同時に、フェッチ時にキャッチを追加し、その中に例外をスローすると、try ... catchを使用しても安全です。例外フローには完璧な世界はありませんが、単一のパターンは理にかなっており、パターンを組み合わせるのは安全ではありません(パターンとアンチパターンの類似性に合わせて)。
user3053247

1
素晴らしい答えですが、ここで欠陥を見つけました-このパターンはすべてのエラーがPromise.rejectを返すことによって処理されると想定しています-checkCredentials()からスローされる可能性のあるすべての予期しないエラーで何が起こりますか
chenop 2018

1
ええ、あなたは正しい@chenop-それらの予期しないエラーをキャッチするには、try / catchでラップする必要があります
maxwell

@maxwellのケースがわかりません。あなただけが行うように、それを構造化することができませんでしたcheckCredentials(x).then(onFulfilled).catch(e) {}、と持っているcatchハンドルの両方の拒絶ケースとスローされたエラーケースを?
ベンウィーラー、

5

試してみる例。isVersionThrowをfalseに変更して、スローではなく拒否を使用します。

const isVersionThrow = true

class TestClass {
  async testFunction () {
    if (isVersionThrow) {
      console.log('Throw version')
      throw new Error('Fail!')
    } else {
      console.log('Reject version')
      return new Promise((resolve, reject) => {
        reject(new Error('Fail!'))
      })
    }
  }
}

const test = async () => {
  const test = new TestClass()
  try {
    var response = await test.testFunction()
    return response 
  } catch (error) {
    console.log('ERROR RETURNED')
    throw error 
  }  
}

test()
.then(result => {
  console.log('result: ' + result)
})
.catch(error => {
  console.log('error: ' + error)
})

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.