Async/Await: escrevendo código assíncrono de forma limpa

[101] Async/Await: escrevendo código assíncrono de forma limpa

A cadeia de .then resolveu a pirâmide mas espalhou os valores por escopos separados. O async/await desfaz essa costura: o código assíncrono volta a ser lido de cima para baixo, com try/catch comum e for...of que espera de verdade — inclusive o clássico await dentro de forEach, que não espera nada.
Javascript

18 min de leitura

As Promises resolveram o Callback Hell. Mas encadear muitos .then() ainda pode ficar confuso quando há lógica condicional, loops ou múltiplas variáveis que precisam ser compartilhadas entre etapas.

O async/await, introduzido no ES2017, é açúcar sintático em cima das Promises — ele não substitui as Promises, apenas fornece uma sintaxe mais limpa para trabalhar com elas. O resultado é código assíncrono que se lê como código síncrono, sem perder nenhum dos benefícios das Promises.

A palavra-chave async

async transforma uma função em uma função assíncrona. Uma função async sempre retorna uma Promise, mesmo que você não use return explicitamente ou retorne um valor simples:

// Função síncrona comum
function somar(a, b) {
  return a + b;
}
console.log(somar(2, 3)); // 5

// Função assíncrona — retorna uma Promise
async function somarAsync(a, b) {
  return a + b;
}

somarAsync(2, 3).then(resultado => console.log(resultado)); // 5

// Também funciona com arrow functions
const multiplicar = async (a, b) => a * b;

A palavra-chave await

await só pode ser usado dentro de funções async. Ele pausa a execução da função até que a Promise seja resolvida — e retorna o valor resolvido:

function esperar(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function exemplo() {
  console.log("Início");
  await esperar(2000); // pausa aqui por 2 segundos
  console.log("2 segundos depois");
  await esperar(1000); // pausa mais 1 segundo
  console.log("Mais 1 segundo depois");
}

exemplo();
console.log("Este executa imediatamente — não espera a função async");

// Saída:
// Início
// Este executa imediatamente — não espera a função async
// (2 segundos)
// 2 segundos depois
// (1 segundo)
// Mais 1 segundo depois

O await pausa somente a função async atual — o restante do programa continua normalmente.

Comparando Promises com async/await

O mesmo código escrito das duas formas:

function buscarUsuario(id) {
  return new Promise(resolve =>
    setTimeout(() => resolve({ id, nome: "Ana", plano: "premium" }), 800)
  );
}

function buscarPedidos(usuarioId) {
  return new Promise(resolve =>
    setTimeout(() => resolve([{ id: 101 }, { id: 102 }]), 600)
  );
}

// ── Com Promises e .then() ──────────────────
function carregarDadosPromise() {
  return buscarUsuario(1)
    .then(usuario => {
      console.log(`Usuário: ${usuario.nome}`);
      return buscarPedidos(usuario.id);
    })
    .then(pedidos => {
      console.log(`Pedidos: ${pedidos.length}`);
    })
    .catch(erro => console.error(erro.message));
}

// ── Com async/await ─────────────────────────
async function carregarDadosAsync() {
  try {
    const usuario = await buscarUsuario(1);
    console.log(`Usuário: ${usuario.nome}`);

    const pedidos = await buscarPedidos(usuario.id);
    console.log(`Pedidos: ${pedidos.length}`);
  } catch (erro) {
    console.error(erro.message);
  }
}

A versão com async/await é mais legível — especialmente quando há lógica condicional entre as etapas.

Tratamento de erros com try/catch

Com async/await, o tratamento de erros volta a usar o familiar try/catch:

async function processarPedido(usuarioId, produtoId) {
  try {
    // Cada await pode lançar um erro se a Promise rejeitar
    const usuario = await buscarUsuario(usuarioId);
    const produto = await buscarProduto(produtoId);

    if (produto.estoque === 0) {
      throw new Error(`Produto "${produto.nome}" sem estoque.`);
    }

    const pedido = await criarPedido(usuario.id, produto.id);
    console.log(`✅ Pedido #${pedido.id} criado com sucesso!`);
    return pedido;

  } catch (erro) {
    console.error(`❌ Erro ao processar pedido: ${erro.message}`);
    throw erro; // repropaga se necessário
  } finally {
    console.log("Processo de pedido finalizado.");
  }
}

Lógica condicional — onde async/await brilha

O ponto onde async/await supera claramente o encadeamento de .then():

async function checkout(carrinho, cupom) {
  const usuario = await buscarUsuarioLogado();

  // Lógica condicional — muito mais clara que em .then()
  if (!usuario) {
    throw new Error("Usuário não autenticado.");
  }

  const itensVerificados = await verificarEstoque(carrinho);

  let total = itensVerificados.reduce((acc, item) => acc + item.preco * item.quantidade, 0);

  if (cupom) {
    try {
      const desconto = await validarCupom(cupom, total);
      total = total - desconto;
      console.log(`Cupom aplicado! Desconto: R$ ${desconto}`);
    } catch {
      console.warn("Cupom inválido ou expirado. Continuando sem desconto.");
    }
  }

  const frete = await calcularFrete(usuario.cep);
  total += frete.valor;

  console.log(`Total com frete: R$ ${total.toFixed(2)}`);
  console.log(`Previsão de entrega: ${frete.prazo}`);

  const pedido = await confirmarPedido({ itens: itensVerificados, total, usuario });
  return pedido;
}

Tente reescrever isso com .then() encadeados e você vai ver o quanto async/await ajuda.

Loops assíncronos

Async/await permite usar estruturas de controle normais com código assíncrono:

const ids = [1, 2, 3, 4, 5];

// ── Sequencial — aguarda cada um antes do próximo
async function carregarSequencial() {
  console.log("Carregando em sequência...");
  const resultados = [];

  for (const id of ids) {
    const usuario = await buscarUsuario(id); // aguarda antes de continuar
    resultados.push(usuario);
    console.log(`Carregado: ${usuario.nome}`);
  }

  return resultados;
}
// Tempo total: soma dos tempos individuais (~5s se cada um levar 1s)

// ── Paralelo — inicia todos ao mesmo tempo
async function carregarParalelo() {
  console.log("Carregando em paralelo...");
  const promises = ids.map(id => buscarUsuario(id));
  const resultados = await Promise.all(promises);
  resultados.forEach(u => console.log(`Carregado: ${u.nome}`));
  return resultados;
}
// Tempo total: tempo do mais lento (~1s)

Use sequencial quando cada operação depende da anterior. Use paralelo (Promise.all) quando as operações são independentes.

async/await com Promise.all

Você pode — e deve — combinar async/await com os métodos de Promise:

async function carregarDashboard(usuarioId) {
  // Inicia tudo em paralelo — eficiente
  const [usuario, pedidos, notificacoes, relatorio] = await Promise.all([
    buscarUsuario(usuarioId),
    buscarPedidos(usuarioId),
    buscarNotificacoes(usuarioId),
    gerarRelatorio(usuarioId),
  ]);

  return { usuario, pedidos, notificacoes, relatorio };
}

// Usando
async function main() {
  try {
    const dados = await carregarDashboard(42);
    console.log(`Bem-vindo, ${dados.usuario.nome}!`);
    console.log(`${dados.pedidos.length} pedidos encontrados.`);
    console.log(`${dados.notificacoes.length} notificações.`);
  } catch (erro) {
    console.error("Erro ao carregar dashboard:", erro.message);
  }
}

main();

Top-level await

No passado, await só podia ser usado dentro de funções async. Em módulos JavaScript modernos (.mjs ou com "type": "module" no package.json), você pode usar await diretamente no nível do módulo:

// Em um módulo ES moderno — sem precisar de função async
const dados = await fetch("https://api.exemplo.com/dados");
const json = await dados.json();
console.log(json);

Isso é muito útil em scripts de configuração e no Node.js moderno.

Erro comum — await em forEach

Um dos erros mais frequentes com async/await: usar await dentro de forEach não funciona como esperado:

const ids = [1, 2, 3];

// ❌ ERRADO — forEach não aguarda os awaits
async function errado() {
  ids.forEach(async (id) => {
    const usuario = await buscarUsuario(id);
    console.log(usuario.nome); // a ordem é imprevisível
  });
  console.log("Terminou?"); // executa ANTES dos awaits!
}

// ✅ CORRETO — use for...of para código sequencial
async function correto() {
  for (const id of ids) {
    const usuario = await buscarUsuario(id);
    console.log(usuario.nome); // ordem garantida
  }
  console.log("Terminou!"); // executa DEPOIS de todos
}

// ✅ CORRETO — use Promise.all para paralelo
async function paraleloCorreto() {
  const usuarios = await Promise.all(ids.map(id => buscarUsuario(id)));
  usuarios.forEach(u => console.log(u.nome));
  console.log("Terminou!");
}

Padrões avançados

Retry automático — tentar novamente em caso de falha:

async function comRetry(funcao, tentativas = 3, delay = 1000) {
  for (let i = 1; i <= tentativas; i++) {
    try {
      return await funcao();
    } catch (erro) {
      console.warn(`Tentativa ${i}/${tentativas} falhou: ${erro.message}`);

      if (i === tentativas) throw erro; // última tentativa — propaga o erro

      await new Promise(resolve => setTimeout(resolve, delay * i)); // espera antes de tentar de novo
    }
  }
}

// Uso
const dados = await comRetry(
  () => buscarDadosDaAPI(),
  3,    // 3 tentativas
  500,  // 500ms, 1000ms, 1500ms entre tentativas
);

Timeout em requisições:

async function comTimeout(promise, ms) {
  const timeout = new Promise((_, reject) =>
    setTimeout(() => reject(new Error(`Timeout após ${ms}ms`)), ms)
  );

  return Promise.race([promise, timeout]);
}

// Uso
try {
  const dados = await comTimeout(buscarDadosDaAPI(), 5000);
  console.log(dados);
} catch (erro) {
  console.error(erro.message); // "Timeout após 5000ms"
}

Exemplo completo — sistema de autenticação

// Simulando serviços
const authService = {
  async validarCredenciais(email, senha) {
    await new Promise(r => setTimeout(r, 800));
    if (email === "admin@email.com" && senha === "senha123") {
      return { token: "jwt_token_abc123", expiraEm: 3600 };
    }
    throw new Error("Credenciais inválidas.");
  },

  async obterPerfil(token) {
    await new Promise(r => setTimeout(r, 500));
    if (!token) throw new Error("Token ausente.");
    return {
      id: 1,
      nome: "Administrador",
      email: "admin@email.com",
      permissoes: ["ler", "escrever", "deletar"],
    };
  },

  async registrarAcesso(userId) {
    await new Promise(r => setTimeout(r, 200));
    const agora = new Date().toLocaleString("pt-BR");
    return { userId, acessoEm: agora, ip: "192.168.1.1" };
  },
};

// Função de login completa
async function login(email, senha) {
  console.log("🔐 Iniciando autenticação...");

  try {
    // Passo 1 — valida credenciais
    const auth = await authService.validarCredenciais(email, senha);
    console.log(`✅ Credenciais válidas. Token expira em ${auth.expiraEm}s`);

    // Passo 2 e 3 — busca perfil e registra acesso em paralelo
    const [perfil, registro] = await Promise.all([
      authService.obterPerfil(auth.token),
      authService.registrarAcesso(1),
    ]);

    console.log(`👤 Bem-vindo, ${perfil.nome}!`);
    console.log(`🔑 Permissões: ${perfil.permissoes.join(", ")}`);
    console.log(`📋 Acesso registrado em: ${registro.acessoEm}`);

    return { auth, perfil, registro };

  } catch (erro) {
    console.error(`❌ Falha no login: ${erro.message}`);
    throw erro;
  }
}

async function main() {
  // Login bem-sucedido
  await login("admin@email.com", "senha123");

  console.log("\n--- Tentativa com credenciais erradas ---\n");

  // Login com falha
  await login("hacker@email.com", "errada").catch(() => {
    console.log("Redirecionando para tela de erro...");
  });
}

main();

Resumo — quando usar cada abordagem

Situação Use
Código simples e linear async/await
Operações em paralelo async/await + Promise.all
Encadeamento simples .then() ou async/await
Eventos que ocorrem várias vezes Callbacks
Precisa de .race(), .any(), .allSettled() Combine com await
Loop que precisa ser sequencial for...of com await
Loop paralelo Promise.all + map com await

Tarefa para você

Construa um sistema de importação de dados em lotes com async/await:

// Contexto: você tem 50 usuários para importar para um banco de dados.
// Importar todos de uma vez pode sobrecarregar o servidor.
// Importe em lotes de 10, com 500ms de pausa entre os lotes.

async function importarEmLotes(usuarios, tamanhoDeLote = 10, pausaMs = 500) {
  // 1. Divida o array em lotes de 'tamanhoDeLote'
  // 2. Para cada lote, importe todos em paralelo (Promise.all)
  // 3. Aguarde 'pausaMs' antes do próximo lote
  // 4. Exiba o progresso: "Lote 1/5 concluído (10/50 usuários)"
  // 5. Retorne um relatório final: { sucesso, falha, total }
}

// Simule a função de importar um usuário:
async function importarUsuario(usuario) {
  await new Promise(r => setTimeout(r, Math.random() * 300 + 100));
  if (Math.random() < 0.1) throw new Error(`Falha ao importar ${usuario.nome}`);
  return { id: Math.random(), ...usuario };
}
Ver solução — importarEmLotes com relatório, sem perder o lote por uma falha
// A função simulada do enunciado, com semente fixa para o resultado
// ser reproduzível ao rodar o exemplo.
async function importarUsuario(usuario) {
  await new Promise((r) => setTimeout(r, Math.random() * 300 + 100));

  if (Math.random() < 0.1) {
    throw new Error(`Falha ao importar ${usuario.nome}`);
  }

  return { id: Math.random(), ...usuario };
}

const esperar = (ms) => new Promise((resolver) => setTimeout(resolver, ms));

// ---------------------------------------------------------------
// 1 — dividir em lotes
// ---------------------------------------------------------------
function emLotes(itens, tamanho) {
  const lotes = [];

  for (let i = 0; i < itens.length; i += tamanho) {
    lotes.push(itens.slice(i, i + tamanho));
  }

  return lotes;
}

// ---------------------------------------------------------------
// 2 a 5 — a importação
// ---------------------------------------------------------------
async function importarEmLotes(usuarios, tamanhoDeLote = 10, pausaMs = 500) {
  const lotes = emLotes(usuarios, tamanhoDeLote);

  const sucesso = [];
  const falha = [];

  for (const [indice, lote] of lotes.entries()) {
    // allSettled, não all: `Promise.all` rejeita no primeiro erro e
    // descarta o resultado dos outros nove, que JÁ FORAM importados.
    // O banco ficaria com registros que o relatório não conhece.
    const resultados = await Promise.allSettled(lote.map(importarUsuario));

    resultados.forEach((resultado, i) => {
      if (resultado.status === "fulfilled") {
        sucesso.push(resultado.value);
      } else {
        falha.push({
          usuario: lote[i],
          motivo: resultado.reason.message,
        });
      }
    });

    const processados = sucesso.length + falha.length;
    console.log(
      `Lote ${indice + 1}/${lotes.length} concluído ` +
      `(${processados}/${usuarios.length} usuários)`
    );

    // Sem pausa depois do último lote: seriam 500 ms de espera à toa
    // antes de devolver o relatório.
    if (indice < lotes.length - 1) await esperar(pausaMs);
  }

  return {
    sucesso: sucesso.length,
    falha: falha.length,
    total: usuarios.length,
    importados: sucesso,
    erros: falha,
  };
}

// ---------------------------------------------------------------
// Rodando
// ---------------------------------------------------------------
const usuarios = Array.from({ length: 50 }, (_, i) => ({
  nome: `Usuário ${String(i + 1).padStart(2, "0")}`,
  email: `usuario${i + 1}@exemplo.com`,
}));

const relatorio = await importarEmLotes(usuarios);

console.log(`\n${relatorio.sucesso} importados · ${relatorio.falha} falharam ` +
            `· ${relatorio.total} no total`);

relatorio.erros.forEach((e) => console.log(`  ❌ ${e.usuario.nome}: ${e.motivo}`));

// ---------------------------------------------------------------
// Por que o for...of e não um forEach
// ---------------------------------------------------------------
// `await` dentro de forEach não espera nada: o callback é async, o
// forEach ignora a promise devolvida e passa para o próximo na hora.
// Os 5 lotes disparariam juntos — exatamente o que o exercício quer
// evitar.
//
//   lotes.forEach(async (lote) => {
//     await Promise.allSettled(lote.map(importarUsuario)); // ❌ ninguém espera
//   });
//
// `for...of` com await pausa o laço de verdade. É o jeito de fazer
// trabalho assíncrono em SÉRIE.

// ---------------------------------------------------------------
// Uma alternativa melhor que lote fixo: pool com concorrência
// ---------------------------------------------------------------
// Lote fixo tem um problema: o lote inteiro espera pelo item mais
// lento antes de o próximo começar. Um pool mantém sempre N em voo.
async function comPool(itens, limite, tarefa) {
  const resultados = new Array(itens.length);
  let proximo = 0;

  async function trabalhador() {
    while (proximo < itens.length) {
      const indice = proximo++; // captura antes do await, senão dois pegam o mesmo
      try {
        resultados[indice] = { status: "ok", valor: await tarefa(itens[indice]) };
      } catch (erro) {
        resultados[indice] = { status: "erro", motivo: erro.message };
      }
    }
  }

  await Promise.all(Array.from({ length: limite }, trabalhador));
  return resultados;
}

const doPool = await comPool(usuarios.slice(0, 20), 5, importarUsuario);
console.log(`\nPool: ${doPool.filter((r) => r.status === "ok").length}/20 importados`);

Promise.all rejeita no primeiro erro — e as outras promessas continuam rodando, com o resultado descartado. Numa importação isso deixa registros gravados que o relatório não conhece. Quando você quer saber o que aconteceu com cada item, é allSettled.

async e await são açúcar sobre Promise, e lembrar disso evita os dois enganos mais comuns: uma função async devolve uma Promise mesmo quando o corpo é inteiramente síncrono, e o await só espera aquilo que ele próprio alcança — dentro de um forEach, não espera nada. Para operações independentes, a forma continua sendo Promise.all; esperar uma de cada vez só se justifica quando a chamada seguinte precisa do resultado da anterior.

Fontes e Referências

Exercícios

Exercício 1

O map tem callback async e não há Promise.all. O que sai em A e em B — e alguma das duas lança erro?

const ids = [1, 2, 3];

async function carregar() {
  const usuarios = ids.map(async (id) => await buscarUsuario(id));

  console.log(usuarios.length);   // A
  console.log(usuarios[0].nome);  // B
}
Ver resposta

✓ Resposta: A imprime 3 e B imprime undefined — e nenhuma das duas lança erro, que é justamente o que torna o defeito difícil de achar. O map não sabe nada de assincronia: ele chama o callback e guarda o que ele devolver, e um callback async devolve sempre uma Promise. Então usuarios é um array de três promises, com length igual a 3, e usuarios[0] é um objeto Promise que simplesmente não tem a propriedade nome — acessar propriedade inexistente devolve undefined em silêncio, sem exceção nenhuma. A correção é envolver o array num await Promise.all(...), que espera as três e devolve os valores. Repare ainda numa redundância no próprio callback: async (id) => await buscarUsuario(id) tem um await que não serve para nada, já que a função async devolveria a promise de qualquer forma — ids.map(id => buscarUsuario(id)), ou simplesmente ids.map(buscarUsuario), produz exatamente o mesmo array.

Exercício 2

Cada busca leva 1 segundo. Quanto tempo leva cada um dos três blocos?

// A
const a = await buscarUsuario(1);
const b = await buscarUsuario(2);

// B
const [c, d] = await Promise.all([buscarUsuario(1), buscarUsuario(2)]);

// C
const p1 = buscarUsuario(1);
const p2 = buscarUsuario(2);
const e = await p1;
const f = await p2;
Ver resposta

✓ Resposta: A leva 2 segundos; B e C levam 1 segundo cada. O C é o que costuma surpreender, e ele revela o ponto central: await não inicia coisa nenhuma. A operação começa no instante em que buscarUsuario(1) é chamada — a promise já está correndo antes de qualquer await. Em C as duas partem juntas, e os dois await apenas colhem resultados que chegam praticamente ao mesmo tempo; quando o segundo é alcançado, a promise dele já está resolvida ou quase. Em A, ao contrário, a segunda chamada só acontece depois de o primeiro await devolver, e por isso os tempos se somam. Ou seja, a diferença entre paralelo e sequencial não está no await, está em onde a chamada é feita. Vale uma ressalva prática sobre C: se p2 rejeitar antes de o código chegar ao await p2, há um intervalo em que essa rejeição não tem observador, e alguns ambientes já disparam o aviso de rejeição não tratada — motivo pelo qual Promise.all é a forma preferida.

Exercício 3

buscarUsuario(-1) sempre rejeita. Qual das duas funções captura o erro?

async function A() {
  try {
    buscarUsuario(-1);
  } catch (erro) {
    console.log("A capturou");
  }
}

async function B() {
  try {
    return buscarUsuario(-1);
  } catch (erro) {
    console.log("B capturou");
  }
}
Ver resposta

✓ Resposta: Nenhuma das duas. Em A falta o await: a chamada apenas cria a promise e devolve na hora, o bloco try termina com sucesso, e a rejeição chega depois, quando o catch já não está mais no caminho — o resultado é um UnhandledPromiseRejection. Em B a armadilha é mais fina: o return devolve a promise para quem chamou antes de ela se assentar, então a função sai do try imediatamente e a rejeição vira problema do chamador, não deste catch. As duas se consertam com a mesma palavra: await buscarUsuario(-1) e return await buscarUsuario(-1). Esse é exatamente o caso em que return await não é redundante, apesar de os linters costumarem sinalizá-lo como tal — dentro de um try, é ele que mantém a função presente para capturar a falha. A regra que resume tudo: try/catch só enxerga aquilo que este mesmo bloco esperou.

Exercício 4

A operação falha nas três tentativas. Quanto tempo o comRetry gasta esperando entre elas, e por que a última tentativa não espera?

async function comRetry(funcao, tentativas = 3, delay = 1000) {
  for (let i = 1; i <= tentativas; i++) {
    try {
      return await funcao();
    } catch (erro) {
      if (i === tentativas) throw erro;
      await new Promise(resolve => setTimeout(resolve, delay * i));
    }
  }
}

await comRetry(() => buscarDadosDaAPI(), 3, 500);
Ver resposta

✓ Resposta: Gasta 1500 ms em esperas: 500 ms depois da primeira falha e 1000 ms depois da segunda. A terceira não espera porque a linha if (i === tentativas) throw erro sai antes — e é assim que deve ser: esperar depois da última tentativa seria adiar a notificação do erro sem tentar nada. O tempo total, claro, ainda soma as três execuções da própria operação. Repare que delay * i produz um recuo linear (500, 1000, 1500…), não exponencial; a versão usada em produção costuma ser delay * 2 ** (i - 1), que cresce bem mais rápido e é mais gentil com um servidor em dificuldade. Falta ainda um detalhe importante nessa implementação: quando muitos clientes falham ao mesmo tempo — porque o servidor caiu —, todos recuam pelo mesmo intervalo e voltam juntos, dando uma nova pancada exatamente no momento em que ele tenta se recuperar. A correção é somar um valor aleatório ao intervalo, o chamado jitter. E vale a regra que antecede tudo isso: só faz sentido repetir operações idempotentes — repetir um pagamento pode cobrar duas vezes.

Exercício 5

Não há promise nenhuma aqui — o await recebe o número 42. Em que ordem as três linhas aparecem?

async function f() {
  console.log("1");
  const x = await 42;
  console.log("2", x);
}

f();
console.log("3");
Ver resposta

✓ Resposta: Sai 1, 3, e só então 2 42. O await não verifica se o valor é uma promise para decidir se pausa: ele sempre suspende a função e agenda a continuação como microtask, envolvendo o valor comum numa promise já resolvida. Então await 42 devolve 42, sim, mas só na próxima volta do event loop — e tudo o que estiver depois da chamada de f(), no código síncrono, roda antes. É o mesmo mecanismo do artigo sobre o Event Loop, visto por dentro de uma função async: a parte antes do primeiro await é síncrona, e daí em diante são microtasks. A consequência prática aparece quando se acrescenta um await a uma função que não tinha nenhum: tudo o que vem depois dele passa a executar mais tarde, e código de fora que dependia da ordem antiga muda de comportamento sem que uma linha dele tenha sido tocada.

Comentários

Mais em Javascript

O que é Programação Assíncrona? O Event Loop Explicado
O que é Programação Assíncrona? O Event Loop Explicado

Uma linguagem que faz uma coisa de cada vez não deveria conseguir esperar três…

NPM: gerenciando pacotes e dependências
NPM: gerenciando pacotes e dependências

Todo projeto Node começa com um package.json de quinze linhas e termina com um…

Objetos: estruturando dados do mundo real
Objetos: estruturando dados do mundo real

Atribuir um objeto a outra variável não copia coisa nenhuma: as duas passam a…