versuche / fange Blöcke mit async / await


116

Ich beschäftige mich mit der asynchronen / wartenden Funktion von Knoten 7 und stolpere immer wieder über Code wie diesen

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();

Dies scheint die einzige Möglichkeit zu sein, mit async / await aufzulösen / abzulehnen oder zurückzugeben / zu werfen. V8 optimiert jedoch den Code in try / catch-Blöcken nicht?!

Gibt es Alternativen?


Was bedeutet "Werfen nach einem Warten ist nicht erfolgreich"? Wenn es Fehler gibt? Wenn das erwartete Ergebnis nicht zurückgegeben wird? Sie könnten in den Fangblock werfen.
DevDig

afaik v8 optimieren versuchen / fangen, eine Wurfaussage ist die langsame
Tamas Hegedus

1
Ich verstehe die Frage immer noch nicht. Sie können alte Versprechen verketten, aber ich glaube nicht, dass es schneller gehen würde. Sie sind also besorgt über die Leistung von Try-Catch? Was hat es dann mit asynchronem Warten zu tun?
Tamas Hegedus

Überprüfen Sie meine Antwort Ich habe versucht, einen saubereren Ansatz zu bekommen
zardilior

Hier können Sie dies tun stackoverflow.com/a/61833084/6482248 Es sieht sauberer aus
Prathamesh More

Antworten:


133

Alternativen

Eine Alternative dazu:

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

wäre so etwas, wenn man Versprechen explizit verwendet:

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

oder so ähnlich, unter Verwendung des Continuation-Passing-Stils:

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

Originalbeispiel

Ihr ursprünglicher Code setzt die Ausführung aus und wartet, bis das zurückgegebene Versprechen erfüllt ist getQuote(). Anschließend wird die Ausführung fortgesetzt und der zurückgegebene Wert in geschrieben var quoteund dann gedruckt, wenn das Versprechen aufgelöst wurde, oder es wird eine Ausnahme ausgelöst und der catch-Block ausgeführt, der den Fehler druckt, wenn das Versprechen abgelehnt wurde.

Mit der Promise-API können Sie dasselbe direkt wie im zweiten Beispiel tun.

Performance

Nun zur Aufführung. Lass es uns testen!

Ich habe gerade diesen Code geschrieben - f1()gibt 1als Rückgabewert, f2()wirft 1als Ausnahme:

function f1() {
  return 1;
}

function f2() {
  throw 1;
}

Rufen wir jetzt den gleichen Code millionenfach auf, zuerst mit f1():

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

Und dann wechseln wir f1()zu f2():

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

Dies ist das Ergebnis, für das ich bekommen habe f1:

$ time node throw-test.js 
1000000

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

Dafür habe ich f2:

$ time node throw-test.js 
1000000

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

Es scheint, dass Sie in einem Single-Thread-Prozess etwa 2 Millionen Würfe pro Sekunde ausführen können. Wenn Sie mehr als das tun, müssen Sie sich möglicherweise darum kümmern.

Zusammenfassung

Ich würde mir über solche Dinge in Node keine Sorgen machen. Wenn solche Dinge häufig verwendet werden, werden sie irgendwann von den V8-, SpiderMonkey- oder Chakra-Teams optimiert und jeder wird folgen - es ist nicht so, als wäre es nicht als Prinzip optimiert, es ist einfach kein Problem.

Selbst wenn es nicht optimiert ist, würde ich dennoch argumentieren, dass Sie, wenn Sie Ihre CPU in Node maximal nutzen, Ihre Zahl wahrscheinlich in C schreiben sollten - dafür sind unter anderem die nativen Addons gedacht. Oder vielleicht wären Dinge wie node.native besser für den Job geeignet als Node.js.

Ich frage mich, was ein Anwendungsfall wäre, der so viele Ausnahmen auslösen muss. Normalerweise ist das Auslösen einer Ausnahme anstelle der Rückgabe eines Werts eine Ausnahme.


Ich weiß, dass der Code leicht mit Promises geschrieben werden kann, wie bereits erwähnt. Ich habe ihn an verschiedenen Beispielen gesehen. Deshalb frage ich. Eine einzelne Operation in try / catch zu haben, ist möglicherweise kein Problem, aber möglicherweise mehrere asynchrone / warten-Funktionen mit weiterer Anwendungslogik.
Patrick

4
@Patrick "könnte sein" und "wird sein" ist ein Unterschied zwischen Spekulation und tatsächlichem Testen. Ich habe es für eine einzelne Anweisung getestet, da dies in Ihrer Frage enthalten war. Sie können meine Beispiele jedoch problemlos konvertieren, um mehrere Anweisungen zu testen. Ich habe auch mehrere andere Optionen zum Schreiben von asynchronem Code bereitgestellt, nach denen Sie ebenfalls gefragt haben. Wenn es Ihre Frage beantwortet, können Sie die Antwort akzeptieren . Um es zusammenzufassen: Ausnahmen sind natürlich langsamer als Rückgaben, aber ihre Verwendung sollte eine Ausnahme sein.
rsp

1
Das Auslösen einer Ausnahme soll in der Tat eine Ausnahme sein. Davon abgesehen ist der Code nicht optimiert, ob Sie eine Ausnahme auslösen oder nicht. Der Leistungseinbruch ergibt sich aus der Verwendung try catchund nicht aus dem Auslösen einer Ausnahme. Obwohl die Zahlen klein sind, ist es nach Ihren Tests fast zehnmal langsamer, was nicht unerheblich ist.
Nepoxx

21

Eine Alternative zum Try-Catch-Block ist wait-to-js lib. Ich benutze es oft. Beispielsweise:

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

}

Diese Syntax ist im Vergleich zu Try-Catch viel sauberer.


Versuchte dies und liebte es. Sauberer und lesbarer Code auf Kosten der Installation eines neuen Moduls. Aber wenn Sie vorhaben, viele asynchrone Funktionen zu schreiben, muss ich sagen, dass dies eine großartige Ergänzung ist! Danke
Filipbarak

21

Alternative ähnlich der Fehlerbehandlung in Golang

Da async / await Versprechen unter der Haube verwendet, können Sie eine kleine Dienstprogrammfunktion wie die folgende schreiben:

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

Importieren Sie es dann, wann immer Sie Fehler abfangen müssen, und schließen Sie Ihre asynchrone Funktion ein, die ein Versprechen zurückgibt.

import catchEm from 'utility';

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

Ich habe ein NPM-Paket erstellt, das genau das oben Genannte
Mike

2
@ Mike Sie könnten das Rad neu erfinden - es gibt bereits ein beliebtes Paket, das genau dies tut: npmjs.com/package/await-to-js
Jakub Kukul

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

  if (getQuoteError) return console.error(err)

  console.log(quote)
}

Anstatt eine mögliche Variable zu deklarieren, um einen Fehler oben zu halten, können Sie dies auch tun

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

Dies funktioniert jedoch nicht, wenn so etwas wie ein TypeError- oder Reference-Fehler ausgelöst wird. Sie können jedoch sicherstellen, dass es sich um einen regelmäßigen Fehler handelt

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

Ich bevorzuge es, alles in einen großen Try-Catch-Block zu packen, in dem mehrere Versprechen erstellt werden. Dies kann es umständlich machen, den Fehler speziell für das Versprechen zu behandeln, das ihn erstellt hat. Die Alternative sind mehrere Try-Catch-Blöcke, die ich gleichermaßen umständlich finde


8

Eine sauberere Alternative wäre die folgende:

Aufgrund der Tatsache, dass jede asynchrone Funktion technisch ein Versprechen ist

Sie können Funktionen Catches hinzufügen, wenn Sie sie mit wait aufrufen

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'))

Sie müssen nicht versuchen, zu fangen, da alle Versprechungsfehler behandelt werden und Sie keine Codefehler haben. Sie können dies im übergeordneten Element weglassen!

Nehmen wir an, Sie arbeiten mit Mongodb. Wenn ein Fehler auftritt, ziehen Sie es möglicherweise vor, ihn in der aufrufenden Funktion zu behandeln, als Wrapper zu erstellen oder Try-Catches zu verwenden.


Sie haben 3 Funktionen. Eine ruft Werte ab und fängt den Fehler ab, eine andere, die Sie zurückgeben, wenn kein Fehler vorliegt, und schließlich einen Aufruf der ersten Funktion mit einem Rückruf, um zu überprüfen, ob diese einen Fehler zurückgegeben hat. All dies wird durch ein einziges "Versprechen" gelöst. Dann (cb) .catch (cb) oder trycatch Block.
Chief Koshi

@Chiefkoshi Wie Sie sehen, würde ein einzelner Fang nicht ausreichen, da der Fehler in allen drei Fällen unterschiedlich behandelt wird. Wenn der erste fehlschlägt, wird d () zurückgegeben. Wenn der zweite fehlschlägt, wird null zurückgegeben, wenn der letzte fehlschlägt, wird eine andere Fehlermeldung angezeigt. Die Frage fragt nach der Behandlung von Fehlern bei der Verwendung von wait. Das ist also auch die Antwort. Alle sollten ausgeführt werden, wenn einer ausfällt. Versuchen Sie, Fangblöcke zu verwenden, würden drei von ihnen in diesem speziellen Beispiel erfordern, das nicht sauberer ist
zardilior

1
Die Frage verlangt nicht die Ausführung nach fehlgeschlagenen Versprechungen. Hier warten Sie auf B, führen dann C aus und geben D zurück, wenn sie fehlerhaft sind. Wie ist das sauberer? C muss auf B warten, aber sie sind unabhängig voneinander. Ich sehe keinen Grund, warum sie zusammen in A wären, wenn sie unabhängig wären. Wenn sie voneinander abhängig wären, würden Sie die Ausführung von C stoppen wollen, wenn B fehlschlägt, den Job von .then.catch oder try-catch. Ich gehe davon aus, dass sie nichts zurückgeben und einige asynchrone Aktionen ausführen, die völlig unabhängig von A sind. Warum werden sie mit asynchronem Warten aufgerufen?
Chief Koshi

Die Frage betrifft Alternativen, um Catch-Blöcke zu versuchen, um Fehler bei der Verwendung von async / await zu behandeln. Das Beispiel hier soll beschreibend sein und ist nichts als ein Beispiel. Es zeigt die individuelle Handhabung unabhängiger Operationen in einer sequentiellen Weise, wie normalerweise Async / Warten verwendet wird. Warum sie mit asynchronem Warten aufgerufen werden, soll nur zeigen, wie damit umgegangen werden kann. Es ist mehr als gerechtfertigt.
Zardilior

2

Ich würde gerne so machen :)

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

Es ist ähnlich wie beim Umgang mit Fehlern co

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

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

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

Code ist nicht sehr klar Mann, sieht aber interessant aus, könnten Sie bearbeiten?
Zardilior

Es ist bedauerlich, dass diese Antwort keine Erklärung enthält, da sie tatsächlich eine großartige Möglichkeit darstellt, zu vermeiden, dass versucht wird, jede Konstante zu fangen, mit der Sie sie zuweisen await!
Jim

0

catchAuf diese Weise ist es meiner Erfahrung nach gefährlich. Jeder Fehler, der im gesamten Stapel ausgelöst wird, wird abgefangen, nicht nur ein Fehler aus diesem Versprechen (was wahrscheinlich nicht das ist, was Sie wollen).

Das zweite Argument für ein Versprechen ist bereits ein Rückruf zur Ablehnung / zum Scheitern. Es ist besser und sicherer, das stattdessen zu verwenden.

Hier ist ein typsicherer typsicherer Einzeiler, den ich geschrieben habe, um dies zu handhaben:

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()
);
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.