Async

Escopo: JavaScript. Idiomas específicos deste ecossistema.

Toda operação que depende de I/O (Input/Output, Entrada/Saída) é assíncrona. Bloquear o thread principal trava a aplicação.

Conceitos fundamentais

ConceitoO que é
I/O (Input/Output, Entrada/Saída)Operação que atravessa o limite do processo: rede, disco, banco
callback (função de retorno)Função passada como argumento para executar quando a operação termina
Promise (promessa de valor)Objeto que representa o resultado futuro de uma operação assíncrona
API (Application Programming Interface, Interface de Programação de Aplicações)Contrato público de uma biblioteca ou serviço externo

Callback hell

❌ Ruim: aninhamento cresce sem controle
function fetchUserData(id, callback) {
  getUser(id, (user) => {
    getOrders(user.id, (orders) => {
      getInvoices(orders[0].id, (invoices) => {
        callback({ user, orders, invoices });
      });
    });
  });
}
✅ Bom: async/await, linear e legível
async function fetchUserData(id) {
  const user = await getUser(id);
  const orders = await getOrders(user.id);

  const invoices = await getInvoices(orders[0].id);

  const userData = { user, orders, invoices };

  return userData;
}

.then() encadeado

❌ Ruim: verboso, difícil de depurar
function fetchUserData(id) {
  return getUser(id)
    .then((user) => getOrders(user.id).then((orders) => ({ user, orders })))
    .then(({ user, orders }) =>
      getInvoices(orders[0].id).then((invoices) => ({ user, orders, invoices }))
    );
}
✅ Bom: mesmo resultado, sem o ruído
async function fetchUserData(id) {
  const user = await getUser(id);
  const orders = await getOrders(user.id);

  const invoices = await getInvoices(orders[0].id);

  const userData = { user, orders, invoices };

  return userData;
}

Bloqueio síncrono

❌ Ruim: loop síncrono trava o thread principal
function wait(ms) {
  const end = Date.now() + ms;
  while (Date.now() < end) {} // congela tudo durante ms milissegundos
}

wait(3000); // aplicação trava por 3 segundos
✅ Bom: Promise libera o thread enquanto aguarda
function wait(ms) {
  const timer = new Promise((resolve) => setTimeout(resolve, ms));
  return timer;
}

async function run() {
  await wait(3000);
  console.log("done");
}

Promise.all: execução paralela

Quando as operações são independentes entre si, rodá-las em paralelo reduz o tempo total de espera.

❌ Ruim: await sequencial quando não há dependência
async function fetchDashboard(userId) {
  const orders = await fetchOrders(userId);     // espera terminar
  const invoices = await fetchInvoices(userId); // só começa depois

  const profile = await fetchProfile(userId);   // só começa depois

  const dashboard = { orders, invoices, profile };

  return dashboard;
}
✅ Bom: Promise.all dispara tudo ao mesmo tempo
async function fetchDashboard(userId) {
  const requests = [
    fetchOrders(userId),
    fetchInvoices(userId),
    fetchProfile(userId),
  ];
  const [orders, invoices, profile] = await Promise.all(requests);

  const dashboard = { orders, invoices, profile };

  return dashboard;
}

Use Promise.all quando as operações não dependem umas das outras. Se uma falhar, todas falham. Use Promise.allSettled quando quiser continuar mesmo com erros parciais.

API client centralizado

Um único cliente carrega a configuração base. Os módulos recebem o cliente por injeção: sem fetch solto espalhado pelo código.

❌ Ruim: fetch direto, configuração duplicada em todo lugar
// user.service.js
async function fetchUser(id) {
  const response = await fetch(`https://api.example.com/users/${id}`, {
    headers: { Authorization: `Bearer ${token}` },
  });
  const user = await response.json();

  return user;
}

// order.service.js
async function fetchOrders(userId) {
  const response = await fetch(`https://api.example.com/orders?userId=${userId}`, {
    headers: { Authorization: `Bearer ${token}` },
  });
  const orders = await response.json();

  return orders;
}
✅ Bom: cliente único, injetado onde precisar
// api.client.js
function createApiClient(baseUrl, token) {
  async function get(path) {
    const fetchConfig = { headers: { Authorization: `Bearer ${token}` } };
    const response = await fetch(`${baseUrl}${path}`, fetchConfig);

    const body = await response.json();
    return body;
  }

  const client = { get };
  return client;
}

export const apiClient = createApiClient("https://api.example.com", token);
// user.service.js
async function fetchUser(apiClient, id) {
  const user = await apiClient.get(`/users/${id}`);

  return user;
}

// order.service.js
async function fetchOrders(apiClient, userId) {
  const orders = await apiClient.get(`/orders?userId=${userId}`);

  return orders;
}

Quando criar uma função async

❌ Ruim: I/O síncrono bloqueia o event loop
// banco de dados síncrono: não existe, mas ilustra o padrão errado
function findUser(id) {
  const user = database.querySync("SELECT * FROM users WHERE id = $1", [id]);
  return user;
}

// leitura de arquivo síncrona trava o processo
function readConfig() {
  const config = fs.readFileSync("./config.json", "utf-8");
  return config;
}
✅ Bom: toda operação de I/O é async
// banco de dados
async function findUser(id) {
  const user = await database.query("SELECT * FROM users WHERE id = $1", [id]);
  return user;
}

// API externa
async function fetchRates() {
  const response = await fetch("https://api.example.com/rates");
  const rates = await response.json();

  return rates;
}

// leitura de arquivo
async function readConfig() {
  const config = await fs.promises.readFile("./config.json", "utf-8");
  return config;
}

Desenvolvido por @thiagocajadev · Fork baseado no repositório pmndrs/docs · Poimandres.