web-dev-qa-db-fra.com

essayer / attraper des blocs avec async / wait

Je creuse dans la fonctionnalité asynchrone/wait du noeud 7 et continue à trébucher sur du code comme celui-ci

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

Cela semble être la seule possibilité de résoudre/rejeter ou de renvoyer/jeter avec async/wait, cependant, la v8 n'optimise pas le code dans les blocs try/catch ?!

Y a-t-il des alternatives?

94
Patrick

Des alternatives

Une alternative à ceci:

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

serait quelque chose comme ceci, en utilisant explicitement les promesses:

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

ou quelque chose comme ça, en utilisant le style de continuation:

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

Exemple original

Ce que votre code original fait, c'est suspendre l'exécution et attendre que la promesse retournée par getQuote() soit réglée. Il continue ensuite l'exécution et écrit la valeur renvoyée sur var quote, puis l'imprime si la promesse a été résolue ou lève une exception et exécute le bloc catch qui affiche l'erreur si la promesse a été rejetée.

Vous pouvez faire la même chose en utilisant l'API Promise directement, comme dans le deuxième exemple.

Performance

Maintenant, pour la performance. Testons-le!

Je viens d'écrire ce code - f1() donne 1 comme valeur de retour, f2() lève 1 comme exception:

function f1() {
  return 1;
}

function f2() {
  throw 1;
}

Appelons maintenant le même code des millions de fois, d’abord avec f1():

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

Et ensuite, changeons f1() en f2():

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

Voici le résultat obtenu pour f1:

$ time node throw-test.js 
1000000

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

Voici ce que j'ai pour f2:

$ time node throw-test.js 
1000000

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

Il semble que vous puissiez faire quelque chose comme 2 millions de lancers par seconde en un seul processus. Si vous faites plus que cela, vous devrez peut-être vous en préoccuper.

Sommaire

Je ne m'inquiéterais pas de ce genre de choses dans Node. Si de telles choses sont beaucoup utilisées, les équipes V8, SpiderMonkey ou Chakra le suivront et tout le monde suivra. Ce n’est pas comme si ce n’était pas un principe optimisé, ce n’est tout simplement pas un problème.

Même si ce n'est pas optimisé, je dirais toujours que si vous maximisez votre processeur en Node, alors vous devriez probablement écrire votre calcul en chiffres C - c'est ce à quoi servent les addons natifs, parmi autres choses. Ou peut-être que des choses comme node.native conviendraient mieux pour le travail que Node.js.

Je me demande quel cas d'utilisation aurait besoin de lever autant d'exceptions. Généralement, le fait de lancer une exception au lieu de renvoyer une valeur est une exception.

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

  if (getQuoteError) return console.error(err)

  console.log(quote)
}

Alternativement, au lieu de déclarer une variable possible pour conserver une erreur en haut, vous pouvez le faire.

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

Bien que cela ne fonctionne pas si quelque chose comme une erreur TypeError ou Reference est renvoyée. Vous pouvez vous assurer que c’est une erreur régulière avec

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

Ma préférence pour ceci est de tout encapsuler dans un gros bloc try-catch où de multiples promesses sont créées, ce qui peut rendre difficile le traitement de l'erreur spécifiquement liée à la promesse qui l'a créée. L'alternative étant plusieurs blocs try-catch que je trouve tout aussi encombrant

14
Tony

Une alternative au bloc try-catch est wait-to-js lib. Je l'utilise souvent. Par exemple:

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

}

Cette syntaxe est beaucoup plus propre comparée à try-catch.

14
Pulkit chadha

Alternative similaire à la gestion des erreurs à Golang

Comme async/wait utilise des promesses sous le capot, vous pouvez écrire une petite fonction utilitaire comme celle-ci:

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

Puis importez-le chaque fois que vous avez besoin de détecter certaines erreurs et enveloppez votre fonction asynchrone qui renvoie une promesse avec.

import catchEm from 'utility';

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

Une alternative plus propre serait la suivante:

En raison du fait que chaque fonction asynchrone est techniquement une promesse

Vous pouvez ajouter des captures aux fonctions lorsque vous les appelez avec wait

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

Pas besoin d'essayer catch, car toutes les erreurs de promesses sont gérées, et vous n'avez aucune erreur de code, vous pouvez l'omettre dans le parent !!

Disons que vous travaillez avec mongodb. S'il y a une erreur, vous préférerez peut-être la manipuler dans la fonction qui l'appelle plutôt que de créer des wrappers ou d'utiliser des captures d'essai.

4
zardilior

Je voudrais faire de cette façon :)

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

Cela ressemble à une erreur de manipulation avec co

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

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

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

catchde cette façon, selon mon expérience, est dangereux. Toute erreur générée dans la pile entière sera interceptée, pas simplement une erreur de cette promesse (qui n'est probablement pas ce que vous voulez).

Le deuxième argument d'une promesse est déjà un rappel de rejet/d'échec. Il vaut mieux et plus sûr de l'utiliser à la place.

Voici un one-liner TypeScript typesafe que j’ai écrit pour gérer ceci:

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