非同期/待機構文で拒否する方法は?


282

非同期/待機関数によって返されたプロミスをどのように拒否できますか?

例:もともと

foo(id: string): Promise<A> {
  return new Promise((resolve, reject) => {
    someAsyncPromise().then((value)=>resolve(200)).catch((err)=>reject(400))
  });
}

async / awaitに翻訳する

async foo(id: string): Promise<A> {
  try{
    await someAsyncPromise();
    return 200;
  } catch(error) {//here goes if someAsyncPromise() rejected}
    return 400; //this will result in a resolved promise.
  });
}

では、この場合、この約束を適切に拒否するにはどうすればよいですか?


20
Promiseコンストラクタのアンチパターンを避けてください!最初のスニペットでさえ書かれるべきfoo(id: string): Promise<A> { return someAsyncPromise().then(()=>{ return 200; }, ()=>{ throw 400; }); }
Bergi

10
この質問はTypeScriptとは関係がないので、この質問のコードをバニラJSに変換すると役立つと思います。私がそうした場合、その編集は受け入れられますか?
ジェイコブフォード

回答:


326

あなたの最善の策は、にあるラッピングと拒否約束して結果の値、ラッピング値:throwErrorError

} catch (error) {
    throw new Error(400);
}

throw値だけを指定することもできますが、スタックトレース情報はありません。

} catch (error) {
    throw 400;
}

または、Error値をラップして拒否されたpromiseを返しますが、これは慣用的ではありません。

} catch (error) {
    return Promise.reject(new Error(400));
}

(あるいは単に return Promise.reject(400);、しかし再び、コンテキスト情報はありません。)

(あなたの場合、あなたが使用していてTypeScriptfooの戻り値がであるPromise<A>ので、あなたは使用するでしょうreturn Promise.reject<A>(400 /*or error*/);

ではasync/await状況、その最後はおそらくセマンティック不一致のビットですが、それは作業を行います。

をスローするとError、構文を使用してfooの結果を消費するものでうまく機能しawaitます。

try {
    await foo();
} catch (error) {
    // Here, `error` would be an `Error` (with stack trace, etc.).
    // Whereas if you used `throw 400`, it would just be `400`.
}

12
また、async / awaitは非同期フローを同期構文に戻すことを目的throwとしているため、Promise.reject()IMO よりも優れています。かどうかthrow 400は別の問題です。OPでは400を拒否していますが、Error代わりに拒否する必要があると主張できます。
ユニオン2017

2
はい、ただし、コードチェーンが実際にasync / awaitを使用している場合は、ここで入力するのは難しいです。答えとしてデモをさせてください
unional

1
catchブロックで指定されたエラーとは対照的に、新しいエラーをスローする理由はありますか?
エイドリアンM

1
@sebastian-どういう意味かわかりません。ではasync機能し、何もありませんresolveまたはreject機能を。関数の約束を解決して拒否する慣用的な方法であるreturnおよびがありthrowますasync
TJクラウダー

1
@ Jan-PhilipGehrcke- できます、私はしません。インスタンスを作成し、それnewを明示的にします。また、あなたが持っている場合は、それを離れることができないことに注意してくださいErrorサブクラス(class MyError extends Errorそう)、...
TJクラウダー

146

またcatch()、内部ではまだpromiseが返されるため、非同期操作の呼び出し後に関数をチェーンすることができることにも言及する必要があります。

await foo().catch(error => console.log(error));

このようにして、try/catch気に入らない場合は構文を回避できます。


1
そのため、自分のasync関数を拒否したい場合は、例外をスローしてから、.catch()返されたPromise.reject場合や呼び出された場合と同じようにうまくキャッチしrejectます。私はそれが好きです!
icl7126

7
なぜこれが受け入れられる答えになるのか理解できません。受け入れられた回答を整理するだけでなくawait、1つのルーチンで起こりうるすべての失敗を処理します。それぞれawaitに非常に具体的なケースが必要でない限り、なぜあなたがこのようにそれらを捕らえたいのかわかりません。私だけの謙虚な意見。
edgaralienfoe 2018

1
@jablesauceを使用する場合、各await失敗を個別にキャッチする必要があるだけでなく、エラーに関する約束を拒否するPromiseベースのフレームワークを使用する必要もありました。
Reuven Karasik

うまくいきませんでした。URLが失敗した場合、キャッチブロックに入っていないようです。[応答] = await oauthGet( ${host}/user/permissions/repositories_wrong_url/、accessToken、accessTokenSecret).catch(err => {logger.error( 'Unable to fetch repository permissions'、err); callback(err);})
sn.anurag

1
awaitここではキーワードは必要ありません。
Ashish Rawat

12

promiseを受け取り、エラーがない場合はデータを、エラーがあった場合はエラーを含む配列を返すラッパー関数を作成できます。

function safePromise(promise) {
  return promise.then(data => [ data ]).catch(error => [ null, error ]);
}

ES7非同期関数で次のように使用します。

async function checkItem() {
  const [ item, error ] = await safePromise(getItem(id));
  if (error) { return null; } // handle error and return
  return item; // no error so safe to use item
}

1
素敵なGo構文を試そうとしていますが、エレガントさはあまりありません。私はそれを使用しているコードが、ソリューションから値を吸い出すのに十分なほど難読化されているのを見つけました。
キム

8

非同期関数を作成するより良い方法は、保留中のプロミスを最初から返し、エラーで拒否されたプロミスを吐き出すのではなく、プロミスのコールバック内で拒否と解決の両方を処理することです。例:

async foo(id: string): Promise<A> {
    return new Promise(function(resolve, reject) {
        // execute some code here
        if (success) { // let's say this is a boolean value from line above
            return resolve(success);
        } else {
            return reject(error); // this can be anything, preferably an Error object to catch the stacktrace from this function
        }
    });
}

次に、返されたpromiseでメソッドをチェーンします。

async function bar () {
    try {
        var result = await foo("someID")
        // use the result here
    } catch (error) {
        // handle error here
    }
}

bar()

ソース-このチュートリアル:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise


5
async / awaitの使用について具体的に尋ねられた質問。約束を使用しない
Mak

この答えは、決定的な正解を意味するものではありませんでした。これは、上記の他の回答に対するサポートの回答でした。私はそれをコメントとして書き留めておきますが、コードがあることを考えると、回答フィールドの方が適しています。
OzzyTheGiant 2018年

明確にしていただきありがとうございます。非同期関数の作成方法を示すことは間違いなく役立ちます。awaitを使用するように2番目のコードブロックを更新すると、より関連性が高く、便利になります。乾杯
Mak

返信内容を編集して更新しました。私が何か逃した場合、私に教えてください
マク

4

複数のtry-catchブロックを使用せずに、新しいアプローチで拒否を適切に処理することを提案します。

import to from './to';

async foo(id: string): Promise<A> {
    let err, result;
    [err, result] = await to(someAsyncPromise()); // notice the to() here
    if (err) {
        return 400;
    }
    return 200;
}

どこto.tsの機能をからインポートする必要があります。

export default function to(promise: Promise<any>): Promise<any> {
    return promise.then(data => {
        return [null, data];
    }).catch(err => [err]);
}

クレジットは、次のリンクでDima Grossmanに送られます。


1
私はこの構造をほぼ独占的に使用し(はるかにクリーン)、しばらくの間npmjs.com/package/await-to-jsに存在していた 'to'モジュールがあります。分解された割り当ての前にletを置くだけで、個別の宣言は必要ありません。let [err]=エラーをチェックするだけの場合にも実行できます 。
DKebler

3

これは@TJ Crowderの答えに対する答えではありません。「そして実際に、例外が拒否に変換される場合、それがエラーであるかどうか本当に気になるかどうかはわかりません。エラーのみをスローするという私の理由はおそらく当てはまりません。 」

コードでasync/ を使用している場合awaitでも、Error代わりにで拒否することをお勧めし400ます。

try {
  await foo('a');
}
catch (e) {
  // you would still want `e` to be an `Error` instead of `400`
}

3

私はこれが古い質問であることを知っていますが、スレッドを間違えただけで、エラーと拒否の間で、例外処理を使用しないという頻繁に繰り返されるアドバイスの(少なくとも多くの場合、少なくとも)実行されない合図があるようです予想されるケースに対処します。例として、非同期メソッドがユーザーを認証しようとして認証が失敗した場合、それは拒否(予期される2つのケースの1つ)であり、エラーではありません(たとえば、認証APIが利用できない場合)。

ヘアを分割するだけではないことを確認するために、次のコードを使用して、3つの異なるアプローチのパフォーマンステストを実行しました。

const iterations = 100000;

function getSwitch() {
  return Math.round(Math.random()) === 1;
}

function doSomething(value) {
  return 'something done to ' + value.toString();
}

let processWithThrow = function () {
  if (getSwitch()) {
    throw new Error('foo');
  }
};

let processWithReturn = function () {
  if (getSwitch()) {
    return new Error('bar');
  } else {
    return {}
  }
};

let processWithCustomObject = function () {
  if (getSwitch()) {
    return {type: 'rejection', message: 'quux'};
  } else {
    return {type: 'usable response', value: 'fnord'};
  }
};

function testTryCatch(limit) {
  for (let i = 0; i < limit; i++) {
    try {
      processWithThrow();
    } catch (e) {
      const dummyValue = doSomething(e);
    }
  }
}

function testReturnError(limit) {
  for (let i = 0; i < limit; i++) {
    const returnValue = processWithReturn();
    if (returnValue instanceof Error) {
      const dummyValue = doSomething(returnValue);
    }
  }
}

function testCustomObject(limit) {
  for (let i = 0; i < limit; i++) {
    const returnValue = processWithCustomObject();
    if (returnValue.type === 'rejection') {
      const dummyValue = doSomething(returnValue);
    }
  }
}

let start, end;
start = new Date();
testTryCatch(iterations);
end = new Date();
const interval_1 = end - start;
start = new Date();
testReturnError(iterations);
end = new Date();
const interval_2 = end - start;
start = new Date();
testCustomObject(iterations);
end = new Date();
const interval_3 = end - start;

console.log(`with try/catch: ${interval_1}ms; with returned Error: ${interval_2}ms; with custom object: ${interval_3}ms`);

Javascriptインタープリターに関する私の不確実性のために、そこにあるもののいくつかが含まれています(私は一度に1つのウサギの穴を下るだけです)。たとえば、doSomething関数を含めて、その戻り値をdummyValue条件付きブロックが最適化されないようにた。

私の結果は:

with try/catch: 507ms; with returned Error: 260ms; with custom object: 5ms

小さな最適化を探すのに苦労する価値がない場合がたくさんあることは知っていますが、大規模なシステムでは、これらのことにより大きな累積的な違いが生じる可能性があり、それはかなり厳密な比較です。

SO ...受け入れられた回答のアプローチは、非同期関数内で予測できないエラーを処理する必要がある場合、または拒否が単に「プランBに進む必要がある場合(またはC、またはD…) "カスタムレスポンスオブジェクトの使用を拒否するのが私の好みだと思います。


2
また、Promiseとは異なり、非同期関数はスローされたエラーを非同期関数にバブルするので、その関数の呼び出しが外側のスコープのtry / catchブロック内にある場合は、非同期関数内の予期しないエラーの処理についてストレスを感じる必要はありません。スコープを囲むエラー。それらはそのスコープにローカルなエラーのように処理されます。これはasync / awaitの主な特典の1つです。
RiqueW 2018年

マイクロベンチマークは悪魔です。数字をよく見てください。ここで1ミリ秒の違いに気づくには、何かを1000倍にする必要があります。はい、スロー/キャッチを追加すると、関数が最適化されなくなります。しかし、a)非同期の何かを待っている場合、バックグラウンドで0.0005 Msよりも長い時間がかかる可能性があります。b)ここで1ミリ秒の差を作るには、1000倍にする必要があります。
Jamie Pate
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.