A diferença entre uma função assíncrona e uma função que retorna uma promessa

Há uma diferença pequena, mas bastante importante, entre uma função que simplesmente retorna uma promessa e uma função que foi declarada usando a async .

Dê uma olhada no seguinte trecho 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 você pode ver, ambas as funções têm o mesmo corpo no qual estamos tentando acessar a propriedade de um argumento que não está definido nos dois casos. A única diferença entre as duas funções é que asyncFn declarado usando a async .

Isso significa que o JavaScript garante que a função asnycFn retorne uma promessa (seja bem-sucedida ou falhe), mesmo que ocorra um erro, no nosso caso o bloco .catch() capturará.

No entanto, no caso da função fn , o mecanismo ainda não sabe que a função retornará uma promessa e, portanto, o código não alcançará o bloco .catch() , o erro não será detectado e cairá no console.

Mais exemplo de vida


Eu sei o que você está pensando agora:
"Quando diabos vou cometer esse erro?"

Adivinhou?

Bem, vamos criar um aplicativo simples que faça exatamente isso.

Suponha que tenhamos um aplicativo criado usando o Express e o MongoDB que use o driver MongoDB Node.JS. Se você não confia em mim, coloquei todo o código-fonte neste repositório do Github , para que você possa cloná-lo e executá-lo localmente, mas também duplicarei todo o código aqui.

Aqui está o nosso arquivo 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')) 

Dê uma olhada no bloco .catch() ! É aqui que a mágica (não) ocorrerá.

O arquivo db.js usado para conectar-se ao banco de dados 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 } } 

E, finalmente, temos um arquivo user-model.js no qual apenas uma função getUserById está atualmente definida:

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

Se você olhar app.js para o arquivo app.js , verá que, quando formos para localhost:3000/users/<id> , chamaremos a função getUserById definida no user-model.js , passando o parâmetro id como a solicitação.

Digamos que você vá para o seguinte endereço: localhost:3000/users/1 . O que você acha que vai acontecer a seguir?

Bem, se você respondeu: "Verei um grande erro do MongoClient" - você estava certo. Para ser mais preciso, você verá o seguinte erro: Error: Argument passed in must be a single String of 12 bytes or a string of 24 hex characters .

E você acha que o bloco .catch() será chamado no próximo snippet 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' })) // <===  ! }) // ...  ... 

Não. Ele não será chamado.

E o que acontece se você alterar uma declaração de função para isso?

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

Sim, você começa a entender o que é o quê. Nosso bloco .catch() será chamado e poderemos processar o erro capturado e mostrá-lo ao usuário.

Em vez de uma conclusão


Espero que esta informação tenha sido útil para alguns de vocês. Observe que este artigo não está tentando forçá-lo a sempre usar funções assíncronas - embora elas sejam muito legais. Eles têm seus próprios casos de uso, mas ainda são açúcar sintático sobre promessas.

Eu só queria que você soubesse que às vezes as promessas podem fazer uma grande diferença e quando (sim, não "se") você encontrar o erro discutido neste artigo, saberá o motivo possível para a sua ocorrência.

Nota PS perev.: para o artigo original, um comentário útil foi deixado pelo usuário Craig P Hicks, que (após comentários nos comentários) eu decidi citar aqui:
Gostaria de chamar a atenção para um detalhe (no meu ambiente de desenvolvimento) os erros que ocorrem no corpo do Promise.resolve({<body>}) não Promise.resolve({<body>}) "capturados":

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

mas os erros que ocorrem no corpo da new Promise() (tradução aproximada: na "promessa adequada" original ) são "capturados":

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

Que tal esta declaração:

 async function fn() { <body> } 

semanticamente, essa opção é equivalente a isso:

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

Conseqüentemente, o fragmento de código abaixo detectará erros se o <body> tiver uma new Promise() ( aprox. Transl.: Na "Promessa apropriada" original ):

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

Portanto, para que o exemplo do início do artigo “capture” erros nos dois casos, é necessário retornar não Promise.resolve() , mas new Promise() nas funções:

 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/pt475260/


All Articles