«Eu executo uma função, e ela retorna undefined!» — uma dor familiar?
\nImagine: você está escrevendo código em JavaScript, faz uma requisição a uma API, espera os dados… e recebe undefined. Ou pior — Promise {<pending>}. Você desanima, quer fechar o navegador e virar monge.
Não se apresse. Esse problema aflige até desenvolvedores experientes. Não é à toa que a pergunta «How do I return the response from an asynchronous call?» no Stack Overflow tem milhares de visualizações e centenas de respostas. Hoje vamos resolvê-la de uma vez por todas.
\nNeste artigo, você aprenderá:
\n- \n
- Por que o
returnnão funciona como você está acostumado \n - Como domar a assincronicidade com callbacks, promises e
async/await\n - Qual abordagem escolher para o seu projeto \n
Vamos nessa!
\n\nPor que o return não retorna os dados?
\nVamos direto ao ponto. Aqui está um exemplo clássico:
\n\nfunction getUserData(userId) {\n fetch(`https://api.example.com/users/${userId}`)\n .then(response => response.json())\n .then(data => {\n return data; // Parece que está tudo certo?\n });\n}\n\nconst user = getUserData(42);\nconsole.log(user); // undefined\n\nFamiliar? Você escreveu return data dentro de .then(), mas do lado de fora recebeu undefined. Por quê?
JavaScript é single-threaded e não bloqueante. Quando você chama fetch, ele não espera pela resposta do servidor. Em vez disso, ele «adiia» a execução do callback para depois e passa imediatamente para a próxima linha. E a próxima linha é console.log(user), onde user ainda não foi definido.
Sua função getUserData termina antes que a resposta do servidor chegue. Ela não retorna nada (retorna undefined), e os dados ficam «suspensos no ar».
Como corrigir isso? Existem três maneiras principais.
\n\nManeira 1: Callbacks — o avô da assincronicidade
\nAntigamente, antes das Promises, todas as operações assíncronas eram feitas com callbacks. Você passa uma função que será chamada quando os dados chegarem.
\n\nfunction getUserData(userId, callback) {\n fetch(`https://api.example.com/users/${userId}`)\n .then(response => response.json())\n .then(data => {\n callback(data); // Chama o callback com os dados\n })\n .catch(error => {\n callback(null, error); // Ou passa o erro\n });\n}\n\n// Uso:\ngetUserData(42, (data, error) => {\n if (error) {\n console.error('Erro:', error);\n return;\n }\n console.log('Dados do usuário:', data);\n});\n\nPrós: simplicidade, funciona em qualquer lugar.
\nContras: se você aninhar vários callbacks, terá o «inferno dos callbacks» (callback hell). O código se torna ilegível.
\n\ngetUserData(42, (data1) => {\n getPosts(data1.id, (data2) => {\n getComments(data2[0].id, (data3) => {\n // ... e assim por diante\n });\n });\n});\n\nO JavaScript moderno se afastou disso. Mas entender callbacks é necessário — eles estão na base de todas as outras abordagens.
\n\nManeira 2: Promises — salvadoras da legibilidade
\nUma Promise é um objeto que representa uma «promessa» de retornar um valor no futuro. Ela tem três estados: pending (pendente), fulfilled (resolvida) e rejected (rejeitada).
Voltemos ao nosso exemplo. Em vez de um callback, retornamos a própria Promise:
\n\nfunction getUserData(userId) {\n return fetch(`https://api.example.com/users/${userId}`)\n .then(response => {\n if (!response.ok) {\n throw new Error('Rede não está ok');\n }\n return response.json();\n });\n}\n\n// Uso:\nconst userPromise = getUserData(42);\n\nuserPromise\n .then(data => {\n console.log('Dados do usuário:', data);\n })\n