try / catch blocks con async / await

Estoy investigando la característica de asincronización / espera del nodo 7 y sigo tropezando con códigos como este

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

Esta parece ser la única posibilidad de resolver / rechazar o devolver / lanzar con async / await, sin embargo, v8 no optimiza el código dentro de los bloques try / catch?

¿Hay alternativas?

Alternativas

Una alternativa a esto:

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

sería algo como esto, usando promesas explícitamente:

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

o algo así, usando el estilo de continuación de paso:

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

Ejemplo original

Lo que hace su código original es suspender la ejecución y esperar a que la promesa devuelta por getQuote() resuelva. A continuación, continúa la ejecución y escribe el valor devuelto en var quote y luego lo imprime si se resolvió la promesa, o lanza una excepción y ejecuta el bloque catch que imprime el error si se rechazó la promesa.

Puedes hacer lo mismo usando la API Promise directamente como en el segundo ejemplo.

Actuación

Ahora, para el rendimiento. ¡Probémoslo!

Acabo de escribir este código: f1() da 1 como valor de retorno, f2() arroja 1 como excepción:

 function f1() { return 1; } function f2() { throw 1; } 

Ahora llamemos al mismo código millones de veces, primero con f1() :

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

Y luego cambiemos f1() a f2() :

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

Este es el resultado que obtuve para f1 :

 $ time node throw-test.js 1000000 real 0m0.073s user 0m0.070s sys 0m0.004s 

Esto es lo que obtuve para f2 :

 $ time node throw-test.js 1000000 real 0m0.632s user 0m0.629s sys 0m0.004s 

Parece que puedes hacer algo así como 2 millones de lanzamientos por segundo en un solo proceso. Si está haciendo más que eso, es posible que deba preocuparse por ello.

Resumen

No me preocuparía por cosas así en Node. Si esto se usa mucho, el V8, el SpiderMonkey o el Chakra lo optimizarán, y todos seguirán, no es como si no se hubiera optimizado como principio, simplemente no es un problema.

Incluso si no está optimizado, seguiría argumentando que si está maximizando su CPU en Node, entonces probablemente debería escribir su número de crujidos en C, para eso están los complementos nativos, entre otras cosas. O tal vez cosas como node.native serían más adecuadas para el trabajo que Node.js.

Me pregunto qué sería un caso de uso que necesita arrojar tantas excepciones. Por lo general, lanzar una excepción en lugar de devolver un valor es, bueno, una excepción.

Alternativa similar al manejo de errores en Golang

Debido a que async / await usa promesas bajo el capó, puede escribir una pequeña función de utilidad como esta:

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

Luego, impórtelo cada vez que necesite detectar algunos errores, y ajuste su función asíncrona que devuelve una promesa.

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

Una alternativa al bloque try-catch es await-to-js lib. A menudo lo uso Por ejemplo:

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

Esta syntax es mucho más limpia en comparación con try-catch.

 async function main() { var getQuoteError var quote = await getQuote().catch(err => { getQuoteError = err } if (getQuoteError) return console.error(err) console.log(quote) } 

Alternativamente, en lugar de declarar una posible var para contener un error en la parte superior, puedes hacer

 if (quote instanceOf Error) ... 

Aunque eso no funcionará si se produce algo así como un error TypeError o de referencia. Sin embargo, puedes asegurarte de que es un error regular

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

Mi preferencia para esto es envolver todo en un gran bloque de try-catch donde se crean múltiples promesas que pueden hacer que sea engorroso manejar el error específicamente a la promesa que lo creó. Con la alternativa de que sean múltiples bloques try-catch que me resultan igualmente engorrosos

Me gustaría hacerlo de esta manera 🙂

 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 similar al error de manejo con co

 const test = opts => { return co(function*() { // do sth yield sthError(); return 'ok'; }).catch(err => { console.error(err); }); };