O problema: operações lentas
Quase tudo de interessante em um backend é assíncrono: ler um arquivo, falar com um banco de dados, chamar outra API. O Node teve três estilos para lidar com isso, e vale a pena conhecer os três.
1. Callbacks e o padrão (err, data)
O estilo original do Node: você passa uma função de continuação (callback)
que o Node chama quando termina. Por convenção, o primeiro argumento é o
erro (ou null se tudo deu certo) e o segundo, os dados:
const fs = require("fs");
fs.readFile("dados.txt", "utf8", (err, data) => {
if (err) {
console.error("Falhou:", err.message);
return; // sempre verificar err primeiro!
}
console.log(data);
});
O problema: aninhar vários callbacks leva ao "callback hell", código em forma de pirâmide difícil de ler e de tratar erros.
2. Promises
Uma promise representa um valor que estará disponível no futuro. Encadeia-se
com .then() (sucesso) e .catch() (erro), de forma plana:
const fs = require("fs/promises"); // versão baseada em promises do fs
fs.readFile("dados.txt", "utf8")
.then((data) => console.log(data))
.catch((err) => console.error(err.message));
3. async / await
É açúcar sintático sobre as promises: você escreve código assíncrono que se
lê como síncrono. Uma função async sempre devolve uma promise, e await
pausa até que a promise se resolva. Os erros são capturados com
try / catch normal:
import { readFile } from "fs/promises";
async function ler(caminho) {
try {
const data = await readFile(caminho, "utf8");
return data;
} catch (err) {
console.error("Não foi possível ler:", err.message);
throw err; // ou devolver um valor padrão
}
}
No exercício deste módulo você escreverá funções
asyncque recebem uma fonte de dados e a esperam comawait. Assim você pratica o fluxo assíncrono sem depender de arquivos reais (que não existem neste ambiente).
Exemplos
Uma função async devolve uma promise; espera-se com await
async function dobro(n) {
return n * 2; // mesmo sem await, devolve uma promise
}
const r = await dobro(21);
console.log(r); // 42