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?
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);
}
});
}
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.
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.
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.
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
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.
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
}
}
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.
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);
});
};
catch
de 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()
);