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'))
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
:
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:
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?
Não. Ele não será chamado.
E o que acontece se você alterar uma declaração de função para isso?
module.exports = {
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));
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));
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"));