La diferencia entre una función asincrónica y una función que devuelve una promesa

Hay una pequeña pero importante diferencia entre una función que simplemente devuelve una promesa y una función que se declaró utilizando la async .

Eche un vistazo al siguiente fragmento de código:

 function fn(obj) { const someProp = obj.someProp return Promise.resolve(someProp) } async function asyncFn(obj) { const someProp = obj.someProp return Promise.resolve(someProp) } asyncFn().catch(err => console.error('Catched')) // => 'Catched' fn().catch(err => console.error('Catched')) // => TypeError: Cannot read property 'someProp' of undefined 

Como puede ver, ambas funciones tienen el mismo cuerpo en el que estamos tratando de acceder a la propiedad de un argumento que no está definido en ambos casos. La única diferencia entre las dos funciones es que asyncFn declara utilizando la async .

Esto significa que JavaScript garantiza que la función asnycFn devuelva una promesa (ya sea exitosa o fallida), incluso si se produce un error en ella, en nuestro caso el bloque .catch() detectará.

Sin embargo, en el caso de la función fn , el motor aún no sabe que la función devolverá una promesa y, por lo tanto, el código no alcanzará el bloque .catch() , el error no se detectará y caerá en la consola.

Más ejemplo de vida


Sé lo que estás pensando ahora:
"¿Cuándo diablos cometeré tal error?"

Adivinado?

Bueno, creemos una aplicación simple que haga exactamente eso.

Supongamos que tenemos una aplicación creada con Express y MongoDB que usa el controlador MongoDB Node.JS. Si no confía en mí, he colocado todo el código fuente en este repositorio de Github , para que pueda clonarlo y ejecutarlo localmente, pero también duplicaré todo el código aquí.

Aquí está nuestro archivo app.js :

 // app.js 'use strict' const express = require('express') const db = require('./db') const userModel = require('./models/user-model') const app = express() db.connect() app.get('/users/:id', (req, res) => { return userModel .getUserById(req.params.id) .then(user => res.json(user)) .catch(err => res.status(400).json({ error: 'An error occured' })) // <===  ! }) app.listen(3000, () => console.log('Server is listening')) 

¡Mira de cerca el bloque .catch() ! Aquí es donde ocurrirá la magia (no).

El archivo db.js utiliza para conectarse a la base de datos mongo:

 'use strict' const MongoClient = require('mongodb').MongoClient const url = 'mongodb://localhost:27017' const dbName = 'async-promise-test' const client = new MongoClient(url) let db module.exports = { connect() { return new Promise((resolve, reject) => { client.connect(err => { if (err) return reject(err) console.log('Connected successfully to server') db = client.db(dbName) resolve(db) }) }) }, getDb() { return db } } 

Y finalmente, tenemos un archivo user-model.js en el que actualmente solo se define una función getUserById :

 // models/user-model.js 'use strict' const ObjectId = require('mongodb').ObjectId const db = require('../db') const collectionName = 'users' module.exports = { /** * Get's a user by it's ID * @param {string} id The id of the user * @returns {Promise<Object>} The user object */ getUserById(id) { return db .getDb() .collection(collectionName) .findOne({ _id: new ObjectId(id) }) } } 

Si app.js a mirar el archivo app.js , verá que cuando vamos a localhost:3000/users/<id> llamamos a la función getUserById definida en el user-model.js , pasando el parámetro id como solicitud.

Digamos que usted va a la siguiente dirección: localhost:3000/users/1 . ¿Qué crees que pasará después?

Bueno, si respondiste: "Veré un gran error de MongoClient", tenías razón. Para ser más precisos, verá el siguiente error: Error: Argument passed in must be a single String of 12 bytes or a string of 24 hex characters .

¿Y crees que se .catch() bloque .catch() en el siguiente fragmento de código?

 // app.js // ...  ... app.get('/users/:id', (req, res) => { return userModel .getUserById(req.params.id) .then(user => res.json(user)) .catch(err => res.status(400).json({ error: 'An error occured' })) // <===  ! }) // ...  ... 

No No será llamado.

¿Y qué sucede si cambia una declaración de función a esto?

 module.exports = { //  ,    async    ! async findById(id) { return db .getDb() .collection(collectionName) .findOne({ _id: new ObjectId(id) }) } } 

Sí, comienzas a entender qué es qué. Se .catch() nuestro bloque .catch() , y podremos procesar el error detectado y mostrárselo al usuario.

En lugar de una conclusión


Espero que esta información haya sido útil para algunos de ustedes. Tenga en cuenta que este artículo no intenta obligarlo a usar siempre funciones asincrónicas, aunque son bastante geniales. Tienen sus propios casos de uso, pero siguen siendo azúcar sintáctica por encima de las promesas.

Solo quería que supieras que a veces las promesas pueden hacer una gran diferencia, y cuando (sí, no "si") encuentras el error discutido en este artículo, sabrás la posible razón de su ocurrencia.

Nota PS perev.: al artículo original, se dejó un comentario útil del usuario Craig P Hicks, que (después de los comentarios en los comentarios) decidí citar aquí:
Me gustaría llamar la atención sobre un detalle, (en mi entorno de desarrollo) los errores que ocurren en el cuerpo de Promise.resolve({<body>}) no se "detectan":

 Promise.resolve((()=>{throw "oops"; })()) .catch(e=>console("Catched ",e)); //  .catch()      "" 

pero los errores que ocurren en el cuerpo de la new Promise() ( aprox. traducción: en la "Promesa apropiada" original ) son "atrapados":

 (new Promise((resolve,reject)=>{ resolve((()=>{throw "oops"})()) })) .catch(e=>console.log("Catched ",e)); // Catched oops 

¿Qué tal esta declaración:

 async function fn() { <body> } 

semánticamente, esta opción es equivalente a esto:

 function fn() { return new Promise((resolve,reject)=>{ resolve({ <body> }) }) } 

En consecuencia, el siguiente fragmento de código detectará errores si el <cuerpo> tiene una new Promise() ( aprox. Traducción: en la "Promesa apropiada" original ):

 function fn() { return Promise.resolve({<body}); } 

Por lo tanto, para que el ejemplo del principio del artículo "atrape" los errores en ambos casos, es necesario devolver no Promise.resolve() , sino new Promise() en las funciones:

 function fn(obj) { return new Promise((resolve, reject) => { const someProp = obj.someProp; resolve(someProp); }); } async function asyncFn(obj) { return new Promise((resolve, reject) => { const someProp = obj.someProp; resolve(someProp); }); } asyncFn().catch(err => console.error("Catched")); // => 'Catched' fn().catch(err => console.error("Catched")); // => 'Catched' 

Source: https://habr.com/ru/post/475260/


All Articles