Introdução à Programação Assíncrona em JavaScript
\n\nJavaScript é uma linguagem de programação single-thread. Isso significa que apenas uma operação é executada por vez. No entanto, se todas as operações fossem executadas sequencialmente, o navegador "travaria" ao carregar dados do servidor ou ler um arquivo. É aqui que a programação assíncrona entra em ação.
\n\nA programação assíncrona permite executar operações longas (requisições a APIs, leitura de arquivos, timers) sem bloquear o fluxo principal de execução. Em vez de esperar a conclusão da tarefa, o código continua sendo executado e o resultado é processado posteriormente. Este é um conceito chave para criar aplicações rápidas e responsivas em Node.js.
\n\nNeste guia, abordaremos três abordagens principais para programação assíncrona: callbacks, promises e a sintaxe async/await. Você aprenderá como trabalhar corretamente com código assíncrono e evitar erros comuns de iniciantes.
1. Callbacks: a base da assincronicidade
\n\nUm callback é uma função que é passada como argumento para outra função e é chamada após a conclusão de uma operação assíncrona. Esta é a forma mais antiga e básica de trabalhar com assincronicidade em JavaScript.
\n\nExemplo com setTimeout
\n\nA função embutida setTimeout é um exemplo clássico de operação assíncrona. Ela recebe um callback e um atraso em milissegundos:
console.log('Início');\n\nsetTimeout(() => {\n console.log('2 segundos se passaram');\n}, 2000);\n\nconsole.log('Fim');\n\n// Saída:\n// Início\n// Fim\n// 2 segundos se passaram\n\nObserve que "Fim" é exibido antes da mensagem do callback. Isso é assincronicidade — o código não espera a conclusão do setTimeout.
O problema do "Callback Hell"
\n\nQuando você tem muitas operações assíncronas aninhadas, o código se torna difícil de ler. Esse fenômeno é chamado de "Callback Hell" (Inferno dos Callbacks):
\n\nfs.readFile('file1.txt', (err, data1) => {\n if (err) throw err;\n fs.readFile('file2.txt', (err, data2) => {\n if (err) throw err;\n fs.readFile('file3.txt', (err, data3) => {\n if (err) throw err;\n console.log(data1, data2, data3);\n });\n });\n});\n\nEsse código é difícil de manter e depurar. Felizmente, o JavaScript moderno oferece soluções mais elegantes.
\n\n2. Promises: tratamento elegante de assincronicidade
\n\nPromise (promessa) é um objeto que representa o resultado de uma operação assíncrona. Uma Promise pode estar em um dos três estados:
\n\n- \n
- Pending (pendente) — estado inicial, a operação ainda não foi concluída \n
- Fulfilled (cumprida) — a operação foi concluída com sucesso \n
- Rejected (rejeitada) — a operação foi concluída com erro \n
Criação e uso de Promise
\n\nconst myPromise = new Promise((resolve, reject) => {\n setTimeout(() => {\n const success = true;\n if (success) {\n resolve('Dados recebidos');\n } else {\n reject('Erro ao receber dados');\n }\n }, 2000);\n});\n\nmyPromise\n .then(result => {\n console.log(result); // "Dados recebidos"\n })\n .catch(error => {\n console.error(error); // "Erro ao receber dados"\n })\n .finally(() => {\n console.log('Operação concluída');\n });\n\nEncadeamento de Promises (Chaining)
\n\nPromises podem ser encadeadas, o que ajuda a evitar o "Callback Hell":
\n\nfunction readFile(filename) {\n return new Promise((resolve, reject) => {\n fs.readFile(filename, 'utf8', (err, data) => {\n if (err) reject(err);\n else resolve(data);\n });\n });\n}\n\nreadFile('file1.txt')\n .then(data1 => {\