prova / cattura i blocchi con async / await


116

Sto scavando nella funzionalità asincrona / attesa del nodo 7 e continuo a imbattermi in codici come questo

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

Questa sembra essere l'unica possibilità di risolvere / rifiutare o restituire / lanciare con async / await, tuttavia, la v8 non ottimizza il codice all'interno dei blocchi try / catch ?!

Ci sono alternative?


Cosa significa "lancia dopo un'attesa non riuscita"? Se sbaglia? Se non restituisce il risultato atteso? Potresti rilanciare nel blocco di cattura.
DevDig

afaik v8 ottimizza try / catch, una dichiarazione di lancio è quella lenta
Tamas Hegedus

1
Continuo a non capire la domanda. Hai usato il vecchio concatenamento di promesse, ma non credo che sarebbe più veloce. Quindi sei preoccupato per le prestazioni di try-catch? Allora cosa ha a che fare con Async await?
Tamas Hegedus

Controlla la mia risposta Ho cercato di ottenere un approccio più pulito
zardilior

Qui si può fare questo stackoverflow.com/a/61833084/6482248 Sembra più pulito
Prathamesh Più

Risposte:


133

alternative

Un'alternativa a questo:

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

sarebbe qualcosa di simile, usando le promesse esplicitamente:

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

o qualcosa del genere, usando lo stile di passaggio di continuazione:

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

Esempio originale

Quello che fa il tuo codice originale è sospendere l'esecuzione e attendere che la promessa restituita da si getQuote()concluda. Quindi continua l'esecuzione e scrive il valore restituito, var quotequindi lo stampa se la promessa è stata risolta oppure genera un'eccezione ed esegue il blocco catch che stampa l'errore se la promessa è stata rifiutata.

Puoi fare la stessa cosa usando l'API Promise direttamente come nel secondo esempio.

Prestazione

Ora, per la performance. Proviamolo!

Ho appena scritto questo codice - f1()1come valore di ritorno, f2()genera 1come eccezione:

function f1() {
  return 1;
}

function f2() {
  throw 1;
}

Ora chiamiamo lo stesso codice milioni di volte, prima con f1():

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

E poi passiamo f1()a f2():

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

Questo è il risultato che ho ottenuto per f1:

$ time node throw-test.js 
1000000

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

Questo è quello che ho ottenuto per f2:

$ time node throw-test.js 
1000000

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

Sembra che tu possa fare qualcosa come 2 milioni di lanci al secondo in un processo a thread singolo. Se stai facendo di più, potresti doverti preoccupare.

Sommario

Non mi preoccuperei di cose del genere in Node. Se cose del genere vengono utilizzate molto, alla fine verranno ottimizzate dai team V8 o SpiderMonkey o Chakra e tutti seguiranno - non è che non sia ottimizzato come principio, semplicemente non è un problema.

Anche se non è ottimizzato, direi comunque che se stai massimizzando la tua CPU in Node, dovresti probabilmente scrivere il tuo numero scricchiolio in C - questo è ciò a cui servono gli addon nativi, tra le altre cose. O forse cose come node.native sarebbero più adatte per il lavoro di Node.js.

Mi chiedo quale sarebbe un caso d'uso che necessita di così tante eccezioni. Di solito lanciare un'eccezione invece di restituire un valore è, beh, un'eccezione.


So che il codice può essere facilmente scritto con Promises, come detto, l'ho visto in giro su vari esempi, ecco perché me lo chiedo. Avere un'unica operazione all'interno di try / catch potrebbe non essere un problema, ma potrebbero esserlo più funzioni async / await con ulteriore logica dell'applicazione.
Patrick

4
@Patrick "potrebbe essere" e "sarà" è una differenza tra la speculazione e il test effettivo. L'ho testato per una singola affermazione perché questo è ciò che era nella tua domanda, ma puoi facilmente convertire i miei esempi per testare più affermazioni. Ho anche fornito diverse altre opzioni per scrivere codice asincrono di cui hai chiesto anche tu. Se risponde alla tua domanda, potresti considerare di accettare la risposta . Per riassumere: ovviamente le eccezioni sono più lente dei resi, ma il loro utilizzo dovrebbe essere un'eccezione.
rsp

1
Il lancio di un'eccezione dovrebbe effettivamente essere un'eccezione. Detto questo, il codice non è ottimizzato indipendentemente dal fatto che si generi un'eccezione o meno. Il successo in termini di prestazioni deriva dall'utilizzo try catch, non dal lancio di un'eccezione. Sebbene i numeri siano piccoli, è quasi 10 volte più lento secondo i tuoi test, il che non è insignificante.
Nepoxx

22

Alternativa simile alla gestione degli errori a Golang

Poiché async / await utilizza promesse nascoste, puoi scrivere una piccola funzione di utilità come questa:

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

Quindi importalo ogni volta che devi rilevare alcuni errori e avvolgi la tua funzione asincrona che restituisce una promessa con essa.

import catchEm from 'utility';

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

Ho creato un pacchetto NPM che fa esattamente quanto sopra - npmjs.com/package/@simmo/task
Mike

2
@Mike Potresti reinventare la ruota: esiste già un pacchetto popolare che fa esattamente questo: npmjs.com/package/await-to-js
Jakub Kukul

21

Un'alternativa al blocco try-catch è await-to-js lib. Lo uso spesso. Per esempio:

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

}

Questa sintassi è molto più pulita rispetto a try-catch.


L'ho provato e l'ho adorato. Codice pulito e leggibile a scapito dell'installazione di un nuovo modulo. Ma se hai intenzione di scrivere molte funzioni asincrone, devo dire che questa è un'ottima aggiunta! Grazie
filipbarak

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

  if (getQuoteError) return console.error(err)

  console.log(quote)
}

In alternativa, invece di dichiarare una possibile var per contenere un errore in alto, puoi farlo

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

Sebbene ciò non funzionerà se viene generato qualcosa come un errore TypeError o Reference. Puoi assicurarti che si tratti di un errore normale con

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

La mia preferenza per questo è avvolgere tutto in un grande blocco try-catch in cui vengono create più promesse che possono rendere scomodo gestire l'errore in modo specifico per la promessa che lo ha creato. Con l'alternativa che sono più blocchi try-catch che trovo ugualmente ingombranti


8

Un'alternativa più pulita sarebbe la seguente:

A causa del fatto che ogni funzione asincrona è tecnicamente una promessa

Puoi aggiungere catture alle funzioni quando le chiami con 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'))

Non è necessario provare a catturare, poiché tutti gli errori delle promesse vengono gestiti e non hai errori di codice, puoi ometterlo nel genitore !!

Diciamo che stai lavorando con mongodb, se c'è un errore potresti preferire gestirlo nella funzione che lo chiama piuttosto che creare wrapper o usare try catch.


Hai 3 funzioni. Uno che ottiene valori e rileva l'errore, un altro viene restituito se non ci sono errori e infine una chiamata alla prima funzione con un callback per verificare se quella ha restituito un errore. Tutto questo è risolto da una singola "promessa" .then (cb) .catch (cb) o blocco trycatch.
Capo koshi

@Chiefkoshi Come puoi vedere una singola cattura non andrebbe bene poiché l'errore viene gestito in modo diverso in tutti e tre i casi. Se il primo fallisce restituisce d (), se il secondo fallisce restituisce null se l'ultimo fallisce viene mostrato un messaggio di errore diverso. La domanda richiede la gestione degli errori durante l'utilizzo di await. Quindi questa è anche la risposta. Tutti dovrebbero essere eseguiti se qualcuno fallisce. Prova a catturare i blocchi ne richiederebbe tre in questo particolare esempio che non è più pulito
zardilior

1
La domanda non richiede l'esecuzione dopo le promesse fallite. Qui aspetti B, quindi esegui C e restituisci D se hanno sbagliato. Come è più pulito? C deve aspettare B ma sono indipendenti l'uno dall'altro. Non vedo una ragione per cui sarebbero in A insieme se fossero indipendenti. Se dipendessero l'uno dall'altro, dovresti interrompere l'esecuzione di C se B fallisce, il lavoro di .then.catch o try-catch. Presumo che non restituiscano nulla ed eseguano alcune azioni asincrone completamente estranee ad A. Perché vengono chiamati con async await?
Capo koshi

La domanda riguarda le alternative per provare a catturare i blocchi per gestire gli errori quando si usa async / await. L'esempio qui deve essere descrittivo e non è altro che un esempio. Mostra la gestione individuale delle operazioni indipendenti in modo sequenziale che di solito è il modo in cui vengono utilizzati async / await. Perché vengono chiamati con attesa asincrona, è solo per mostrare come potrebbe essere gestito. È descrittivo più che giustificato.
zardilior

2

Mi piacerebbe farlo in questo modo :)

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

È simile alla gestione dell'errore con co

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

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

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

Il codice non è molto chiaro amico, sembra interessante, potresti modificarlo?
zardilior

È un peccato che non ci sia alcuna spiegazione in questa risposta perché in realtà dimostra un ottimo modo per evitare di provare a catturare ogni const che assegni await!
Jim il

0

catchin questo modo, secondo la mia esperienza, è pericoloso. Qualsiasi errore generato nell'intero stack verrà rilevato, non solo un errore da questa promessa (che probabilmente non è quello che vuoi).

Il secondo argomento di una promessa è già un callback di rifiuto / fallimento. È meglio e più sicuro usarlo invece.

Ecco una riga di battitura dattiloscritta che ho scritto per gestire questo:

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()
);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.