¿Debo lanzar un error o devolver una promesa rechazada dentro de una función asíncrona?

Estoy trabajando con las promesas proporcionadas por el SDK de AWS JS . La esencia de lo que estoy haciendo cuando creo una función asíncrona que envuelve el SDK de AWS se ve así:

module.exports.myCustomFunction = input => { if (badInput) { throw new Error('failed') //  { // carry on }) .catch(err => { // do something with the error }) 

Me contactó alguien que dijo que nunca debería lanzar un error dentro de este tipo de funciones basadas en promesas. Ellos sugirieron devolver Promise.reject('failed') lugar. Para ser honesto, todavía no estoy muy versado en promesas, así que su explicación se me pasó por alto.

Son correctos

La llamada a myCustomFunction supone que se devuelve una promesa en todo momento ( .then y .catch ocupan de las promesas resueltas y rechazadas, respectivamente). Cuando lanzas un error, la función no devuelve una promesa.

Podrías usar esto para atrapar el error:

 try { myModule.myCustomFunction(someInput).then(result => { // carry on }) .catch(err => { // do something with the error }) } catch(err) { ... } 

Pero como puede ver, esto da como resultado dos controladores de errores: try/catch para el error de .catch sincrónico, y .catch para cualquier promesa rechazada que sns.createTopic(someParams) puede devolver.

Por eso es mejor usar Promise.reject() :

 module.exports.myCustomFunction = input => { if (badInput) { return Promise.reject('failed'); } return sns.createTopic(someParams).promise() } 

Luego, el .catch detectará ambos tipos de errores / rechazos.

NB: para las versiones más recientes de Node.js (v7.6 y superior, creo), lo siguiente también funcionará:

 module.exports.myCustomFunction = async input => { if (badInput) { throw new Error('failed'); } return sns.createTopic(someParams).promise() } 

La clave aquí es la palabra clave async . Al usar esta palabra clave, los resultados de la función se envuelven automáticamente con una promesa (similar a lo que muestra la respuesta de @ peteb).

Un throw dentro de una cadena Promesa / Promesa hará que esa Promesa / Cadena Promesa sea rechazada automáticamente.

 const myFunc = input => { return new Promise((resolve, reject) => { if (badInput) { throw new Error('failed') } return resolve(sns.createTopic(input).promise()) }) } return myFunc('some input') .then(result => { // handle result }) .catch(err => { console.log(err) }) // will log 'failed' 

Sin embargo, su myCustomFunction no está envuelto en una Promesa, está usando el throw antes de que la Promesa sea devuelta por sns.createTopic().promise() Promise sns.createTopic().promise() . Para crear y devolver una Promesa ya en un estado rechazado, usaría Promise.reject(new Error('failed')) lugar de throw .

No estoy de acuerdo con quien te dijo

Nunca lance un error dentro de este tipo de funciones basadas en promesas.

En mi opinión, debería lanzar un TypeError() sincrónica, ya que indica un error de progtwigdor en lugar de un error operacional .

Para citar a Joyent | Manejo de errores :

Los errores operativos representan problemas de tiempo de ejecución experimentados por progtwigs escritos correctamente. Estos no son errores en el progtwig. De hecho, estos suelen ser problemas con otra cosa […]

Los errores del progtwigdor son errores en el progtwig. Estas son cosas que siempre se pueden evitar cambiando el código. Nunca se pueden manejar adecuadamente (ya que, por definición, el código en cuestión está roto).

Su colega no puede diferenciar entre estos tipos de errores, y el código que ha escrito es casi como debería ser, con la excepción de usar un Error() genérico Error() lugar del TypeError() semánticamente correcto.

¿Por qué debería importarte la diferencia?

Comenzó diciendo que está escribiendo un contenedor para el SDK de AWS. Entonces, desde el punto de vista de los desarrolladores que usan tu biblioteca, ¿crees que preferirían depurar un progtwig que se lanza inmediatamente donde están haciendo algo mal, o preferirían depurar un progtwig que falla silenciosamente, intentando resolverlo? ¿Su mal uso de su API sin informarles de código incorrecto?

Si crees que la primera opción parece más fácil de manejar, estarías en lo cierto. Su progtwig siempre debe ser lo más transparente posible al decirle a un progtwigdor lo que hicieron mal. Intentar resolver el uso indebido es lo que da como resultado APIs defectuosas con un comportamiento indefinido, no documentado, y simplemente extraño.

¿Qué estaban tratando de recomendar que yo hiciera?

Para dar un ejemplo extremadamente básico (y posiblemente una comparación injusta, ya que no tengo ningún contexto en cuanto a lo que constituye una badInput ), su colega le informa que debe hacer esto:

 try { if (badInput) { throw new Error('failed') } ... } catch (error) { // expected error, proceed as normal // ...except not really, you have a bug } 

en lugar de esto:

 process.on('uncaughtException', function (error) { // deal with programmer errors here and exit gracefully }) if (badInput) { throw new Error('failed') } try { ... } catch (error) { // deal with operational errors here and continue as normal } 

Algunos ejemplos reales en el entorno de ejecución Node.js que diferencian estos errores, incluso en funciones asíncronas, se pueden encontrar en el cuadro aquí :

 Example func | Kind of func | Example error | Kind of error | How to | Caller uses | | | | deliver | ========================================================================================== fs.stat | asynchronous | file not found | operational | callback | handle callback | | | | | error -------------+--------------+----------------+---------------+----------+----------------- fs.stat | asynchronous | null for | programmer | throw | none (crash) | | filename | | | 

Conclusión

Le dejaré que decida si su problema en particular se debe a un error del progtwigdor o un error operacional, pero en general, el consejo que se le dio no es un buen consejo, y alienta a los progtwigs con errores que intentan proceder como si no habia nada malo

TL; DR

Una función que se espera que devuelva una Promise en condiciones operativas debe throw sincrónica cuando el error se debe a un error, y debe reject forma asíncrona cuando se produce el error dentro de un progtwig escrito correctamente.

Esto refleja la recomendación oficial de Joyent :

La mejor manera de recuperarse de los errores del progtwigdor es bloquearse inmediatamente.