async / awaitでブロックを試行/キャッチする


116

私はノード7の非同期/待機機能を掘り下げて、このようなコードに出くわし続けています

function getQuote() {
  let quote = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
  return quote;
}

async function main() {
  try {
    var quote = await getQuote();
    console.log(quote);
  } catch (error) {
    console.error(error);
  }
}

main();

これはasync / awaitで解決/拒否または復帰/スローする唯一の可能性のようですが、v8はtry / catchブロック内のコードを最適化しませんか?

代替案はありますか?


「待機後に失敗した場合のスロー」とはどういう意味ですか?エラーが発生した場合 期待した結果が返されない場合はどうなりますか?キャッチブロックで再スローできます。
DevDig 2016年

afaik v8はtry / catchを最適化しています。スローステートメントは遅いものです
Tamas Hegedus

1
私はまだ質問を理解していません。あなたは古い約束連鎖を使用しますが、私はそれがより速くなるとは思いません。では、try-catchのパフォーマンスを心配していますか?次に、非同期待機とはどういうことですか?
タマスHegedus

私の答えをチェックして、よりクリーンなアプローチを得ようとしました
zardilior

ここでは、これを行うことができますstackoverflow.com/a/61833084/6482248をそれはきれいに見える
Prathameshより

回答:


133

代替案

これの代替:

async function main() {
  try {
    var quote = await getQuote();
    console.log(quote);
  } catch (error) {
    console.error(error);
  }
}

明示的にpromiseを使用すると、次のようになります。

function main() {
  getQuote().then((quote) => {
    console.log(quote);
  }).catch((error) => {
    console.error(error);
  });
}

または、継続渡しスタイルを使用して次のようなもの:

function main() {
  getQuote((error, quote) => {
    if (error) {
      console.error(error);
    } else {
      console.log(quote);
    }
  });
}

元の例

元のコードが実行するのは、実行を一時停止し、が返す約束getQuote()が解決するのを待つことです。次に、実行を継続して戻り値を書き込み、var quotepromiseが解決された場合はそれを出力するか、例外をスローして、promiseが拒否された場合にエラーを出力するcatchブロックを実行します。

2番目の例のように、Promise APIを直接使用して同じことを行うことができます。

パフォーマンス

さて、パフォーマンスのために。テストしてみましょう!

私はこのコードを書いたところです- 戻り値としてf1()与え、例外としてスローします:1f2()1

function f1() {
  return 1;
}

function f2() {
  throw 1;
}

今度は同じコードを100万回呼び出しましょうf1()

var sum = 0;
for (var i = 0; i < 1e6; i++) {
  try {
    sum += f1();
  } catch (e) {
    sum += e;
  }
}
console.log(sum);

次に、次のように変更f1()f2()ます。

var sum = 0;
for (var i = 0; i < 1e6; i++) {
  try {
    sum += f2();
  } catch (e) {
    sum += e;
  }
}
console.log(sum);

これは私が得た結果ですf1

$ time node throw-test.js 
1000000

real    0m0.073s
user    0m0.070s
sys     0m0.004s

これは私が得たものですf2

$ time node throw-test.js 
1000000

real    0m0.632s
user    0m0.629s
sys     0m0.004s

1つのシングルスレッドプロセスで200万スローを1秒にスローするようなことができるようです。それ以上のことをしている場合は、心配する必要があるかもしれません。

概要

Nodeではそのようなことは心配しません。このようなものが頻繁に使用されると、V8、SpiderMonkey、またはChakraチームによって最終的に最適化され、誰もがフォローします。原則として最適化されていないようではなく、単に問題ではありません。

それが最適化されていない場合でも、NodeでCPUを最大限に活用している場合は、Cで数値を計算する必要があると私は主張します。これが、ネイティブアドオンの目的です。あるいは、node.nativeのようなものがNode.jsよりもジョブに適しているでしょう。

非常に多くの例外をスローする必要があるユースケースは何でしょうか。通常、値を返す代わりに例外をスローすることは、例外です。


前述のように、Promiseを使用してコードを簡単に記述できることはわかっています。さまざまな例でコードを見てきたので、私は尋ねています。try / catch内に単一の操作があることは問題ではないかもしれませんが、さらにアプリケーションロジックを備えた複数の非同期/待機関数がある可能性があります。
Patrick、

4
@Patrickは「可能性がある」と「予定されている」は推測と実際のテストの違いです。それがあなたの質問にあったので、それを単一のステートメントでテストしましたが、私の例を簡単に変換して複数のステートメントをテストできます。また、あなたが尋ねた非同期コードを書くために他のいくつかのオプションも提供しました。それがあなたの質問に答えるなら、あなたは答えを受け入れることを検討するかもしれません。まとめると、もちろん例外はリターンよりも遅いですが、それらの使用法は例外であるべきです。
rsp '14年

1
例外を投げることは確かに例外であることになっています。つまり、例外をスローするかどうかにかかわらず、コードは最適化されていません。パフォーマンスへの影響はtry catch、例外のスローではなく、の使用に起因します。数は少ないですが、テストによると、それはほぼ10倍遅くなっています。
Nepoxx

21

Golangのエラー処理に似た代替手段

async / awaitは内部でpromiseを使用するため、次のような小さなユーティリティ関数を作成できます。

export function catchEm(promise) {
  return promise.then(data => [null, data])
    .catch(err => [err]);
}

その後、いくつかのエラーをキャッチする必要があるときにそれをインポートし、それを使用してpromiseを返す非同期関数をラップします。

import catchEm from 'utility';

async performAsyncWork() {
  const [err, data] = await catchEm(asyncFunction(arg1, arg2));
  if (err) {
    // handle errors
  } else {
    // use data
  }
}

上記を正確に実行するNPMパッケージを作成しました-npmjs.com/package/@simmo/task
Mike

2
@Mikeあなたは車輪を再発明しているかもしれません-これを正確に行う人気のあるパッケージがすでにあります:npmjs.com/package/await-to-js
Jakub

21

try-catchブロックの代わりに、await-to-js libがあります。よく使います。例えば:

import to from 'await-to-js';

async function main(callback) {
    const [err,quote] = await to(getQuote());
    
    if(err || !quote) return callback(new Error('No Quote found'));

    callback(null,quote);

}

この構文は、try-catchと比較するとはるかにクリーンです。


これを試して、それを愛しました。新しいモジュールのインストールを犠牲にして、クリーンで読みやすいコード。しかし、もしあなたが多くの非同期関数を書くことを計画しているなら、これは素晴らしい追加だと言わなければなりません!ありがとう
filipbarak 2018年

15
async function main() {
  var getQuoteError
  var quote = await getQuote().catch(err => { getQuoteError = err }

  if (getQuoteError) return console.error(err)

  console.log(quote)
}

または、可能な変数を宣言する代わりに、上部にエラーを保持することができます

if (quote instanceof Error) {
  // ...
}

ただし、TypeErrorやReferenceエラーなどがスローされた場合は機能しません。あなたはそれが定期的なエラーであることを確認することができます

async function main() {
  var quote = await getQuote().catch(err => {
    console.error(err)      

    return new Error('Error getting quote')
  })

  if (quote instanceOf Error) return quote // get out of here or do whatever

  console.log(quote)
}

これに対する私の好みは、作成されている複数のpromiseが存在する大きなtry-catchブロックですべてをラップすることであり、それを作成したpromiseに固有のエラーを処理するのが面倒になります。代わりに、私も同じように扱いにくい複数のtry-catchブロックがあります


8

よりクリーンな代替案は次のとおりです。

すべての非同期機能は技術的には約束されているため

awaitで呼び出すときに、関数にキャッチを追加できます

async function a(){
    let error;

    // log the error on the parent
    await b().catch((err)=>console.log('b.failed'))

    // change an error variable
    await c().catch((err)=>{error=true; console.log(err)})

    // return whatever you want
    return error ? d() : null;
}
a().catch(()=>console.log('main program failed'))

すべてのpromiseエラーが処理され、コードエラーがないので、catchを試行する必要はありません。親では省略できます。

mongodbで作業しているとしましょう。エラーが発生した場合は、ラッパーを作成したり、try catchを使用したりするよりも、関数を呼び出して処理する方が好ましい場合があります。


3つの機能があります。1つは値を取得してエラーをキャッチし、もう1つはエラーがない場合に戻り、最後にコールバックを使用して最初の関数を呼び出し、エラーが返されたかどうかを確認します。これはすべて、単一の "promise" .then(cb).catch(cb)またはtrycatchブロックによって解決されます。
koshiチーフ

@Chiefkoshiご覧のとおり、3つのケースすべてでエラーが異なる方法で処理されているため、単一のキャッチは機能しません。最初の1つが失敗した場合、d()を返します。2つ目が失敗した場合、最後の1つが失敗した場合、nullを返します。別のエラーメッセージが表示されます。この質問では、awaitを使用する場合のエラー処理を求めています。それも答えです。いずれかが失敗すると、すべてが実行されます。catchブロックがきれいではありません、この特定の例では、それらの3を必要とするお試しください
zardilior

1
質問は、約束が失敗した後に実行することを求めていません。ここでは、Bを待ってからCを実行し、エラーが発生した場合はDを返します。このクリーナーはどうですか?CはBを待つ必要がありますが、それらは互いに独立しています。彼らが独立している場合、彼らが一緒にAになる理由はわかりません。それらが互いに依存している場合、Bが失敗した場合にCの実行を停止する必要があります。これは、.then.catchまたはtry-catchのジョブです。私は何も返さず、Aとはまったく無関係の非同期アクションを実行すると思います。なぜ非同期待機で呼び出されるのですか?
チーフ

問題は、async / awaitを使用しているときにエラーを処理するためにcatchブロックを試す代替手段に関するものです。ここでの例は説明のためのものであり、例にすぎません。これは、通常、非同期/待機が使用される方法である、独立した操作の個別の処理を示します。なぜ非同期待機で呼び出されるのかは、その処理方法を示すためだけです。その説明は正当化される以上のものです。
zardilior

2

私はこのようにしたいと思います:)

const sthError = () => Promise.reject('sth error');

const test = opts => {
  return (async () => {

    // do sth
    await sthError();
    return 'ok';

  })().catch(err => {
    console.error(err); // error will be catched there 
  });
};

test().then(ret => {
  console.log(ret);
});

それはでエラーを処理することに似ています co

const test = opts => {
  return co(function*() {

    // do sth
    yield sthError();
    return 'ok';

  }).catch(err => {
    console.error(err);
  });
};

コードはあまり明確ではありませんが、面白そうですが、編集できますか?
zardilior

この答えには説明がないのは残念です。実際に、割り当てたすべてのconstをawait
ジム

0

catch私の経験では、この方法で働くことは危険です。スタック全体でスローされたエラーはキャッチされます。このプロミスからのエラーだけではありません(これはおそらく望んでいるものではありません)。

promiseの2番目の引数は、すでに拒否/失敗のコールバックです。代わりにそれを使用するほうが安全です。

これを処理するために私が書いたtypescript typesafe one-linerは次のとおりです。

function wait<R, E>(promise: Promise<R>): [R | null, E | null] {
  return (promise.then((data: R) => [data, null], (err: E) => [null, err]) as any) as [R, E];
}

// Usage
const [currUser, currUserError] = await wait<GetCurrentUser_user, GetCurrentUser_errors>(
  apiClient.getCurrentUser()
);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.