Promises sem medo + LocalStorage na prática: guia para iniciantes

Quando comecei a estudar desenvolvimento web, confesso que ficava meio perdido com algumas coisas no JavaScript, principalmente quando falavam sobre Promises ou sobre como salvar dados no navegador. Era como se fossem assuntos avançados demais. Mas, com o tempo, fui entendendo que esses conceitos aparecem em situações bem práticas do dia a dia de um dev.

Nesta aula quero compartilhar um pouco do que aprendi sobre dois temas importantes:

  • Promise.all() e Promise.allSettled(), que ajudam a lidar com tarefas assíncronas (como buscar dados de APIs ao mesmo tempo).
  • LocalStorage, uma forma super útil de guardar informações direto no navegador, sem precisar de servidor.

A ideia é explicar de forma simples, com exemplos comentados e analogias fáceis de entender, porque sei como é estar começando e se sentir meio perdido. Espero que esse conteúdo ajude você a clarear esses conceitos e a usar no seu próprio código, então BORA CODAR!!

1) Promises no JavaScript (resumo didático)

O que é uma Promise?

Analogia: pense numa encomenda que você faz online. Você recebe um código de rastreio (a Promise). A encomenda pode:

  • Chegar (fulfilled),
  • Dar problema (rejected), ou
  • Ainda estar a caminho (pending).

A Promise é esse “compromisso” de que, em algum momento, você terá um resultado ou um erro.

Por que usar Promises?

Porque muitas tarefas levam tempo (chamadas de API, leitura de arquivos, timers). Em vez de travar a página esperando, você segue com a vida e trata o resultado quando ele estiver pronto.


1.1) Quando e por que usar Promise.all() e Promise.allSettled()

  • Promise.all(): quando você tem várias tarefas independentes que todas precisam dar certo. Se uma falhar, o conjunto falha. Exemplo: carregar dados de perfil, posts e comentários que todos serão exibidos juntos.
  • Promise.allSettled(): quando você quer sempre receber um relatório completo do que aconteceu — o que deu certo e o que falhou — sem interromper tudo na primeira falha. Exemplo: disparar várias chamadas para serviços não críticos (ex.: métricas) e, no final, decidir o que fazer com cada resultado.

Diferenças em uma tabela simples

AspectoPromise.all()Promise.allSettled()
Quando resolve?Quando todas as Promises resolvemSempre resolve, independente de falhas
O que retorna?Array com os valores de sucesso (na mesma ordem das Promises)Array de objetos { status, value } ou { status, reason }
Como trata erro?Rejeita na primeira falhaNunca rejeita (entrega um relatório por item)
Uso típicoDados essenciais que todos precisam dar certoTarefas onde você quer relatório completo de sucesso/erro

1.2) Exemplos práticos (com explicação linha a linha)

Exemplo A — Chamadas de API simultâneas com fetch e Promise.all()

Cenário: carregar, ao mesmo tempo, dados de usuário e posts. Se uma chamada falhar, paramos tudo porque precisamos das duas.

async function carregarDadosEssenciais() {
  // 1) Disparamos as chamadas em paralelo (não esperamos uma acabar para começar a outra)
  const userPromise = fetch('<https://jsonplaceholder.typicode.com/users/1>');
  const postsPromise = fetch('<https://jsonplaceholder.typicode.com/posts?userId=1>');

  try {
    // 2) Esperamos ambas com Promise.all()
    const [userRes, postsRes] = await Promise.all([userPromise, postsPromise]);

    // 3) Conferimos status HTTP (boa prática)
    if (!userRes.ok || !postsRes.ok) {
      throw new Error('Falha ao buscar dados essenciais');
    }

    // 4) Parse do JSON em paralelo
    const [user, posts] = await Promise.all([userRes.json(), postsRes.json()]);

    // 5) Usamos os resultados
    console.log('Usuário:', user);
    console.log('Posts:', posts);
    return { user, posts };
  } catch (err) {
    // 6) Se QUALQUER uma falhar, caímos aqui
    console.error('Erro geral:', err.message);
    // 7) Mostramos uma mensagem amigável na UI, por exemplo
    // showToast('Não foi possível carregar os dados. Tente novamente.');
    return null;
  }
}

carregarDadosEssenciais();

Explicação linha a linha:

  1. Disparamos duas requisições em paralelo — isso economiza tempo.
  2. Promise.all([...]) espera todas terminarem; se uma falhar, lança erro.
  3. Checamos res.ok para validar HTTP 2xx.
  4. Fazemos o json() também em paralelo com Promise.all.
  5. Temos os objetos prontos para renderizar.
  6. Qualquer erro em rede, status inválido ou parse cai no catch.
  7. Retornamos null e tratamos na interface (ex.: mensagem de erro).

Exemplo B — Simulações com setTimeout e uma Promise que falha

Cenário: três tarefas “demoram” tempos diferentes; uma delas falha. Compare all vs allSettled.

// Utilitário que simula uma tarefa assíncrona
function tarefa(nome, tempoMs, deveFalhar = false) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (deveFalhar) {
        reject(new Error(`Tarefa ${nome} falhou`));
      } else {
        resolve(`Tarefa ${nome} concluída em ${tempoMs}ms`);
      }
    }, tempoMs);
  });
}

async function compararAllEAllSettled() {
  const p1 = tarefa('A', 500);         // sucesso em 500ms
  const p2 = tarefa('B', 800, true);   // falha em 800ms
  const p3 = tarefa('C', 300);         // sucesso em 300ms

  // --- Promise.all ---
  try {
    const resultadosAll = await Promise.all([p1, p2, p3]);
    console.log('ALL:', resultadosAll);
  } catch (e) {
    console.log('ALL -> caiu no catch:', e.message);
  }

  // --- Promise.allSettled ---
  const resultadosSettled = await Promise.allSettled([p1, p2, p3]);
  console.log('ALLSETTLED:', resultadosSettled);
}

compararAllEAllSettled();

Explicação linha a linha:

  • tarefa(...) cria uma Promise que resolve ou rejeita após tempoMs.
  • p1, p2, p3 são disparadas ao mesmo tempo.
  • Com Promise.all, basta uma falhar (p2) para cair no catch.
  • Com Promise.allSettled, você recebe um relatório completo:
    • { status: 'fulfilled', value: '...' } para p1 e p3;
    • { status: 'rejected', reason: Error(...) } para p2.

1.3) Tratando erros em cada método

Promise.all() + async/await

async function carregarTudoOuNada(promises) {
  try {
    const results = await Promise.all(promises);
    return results; // todos os resultados OK
  } catch (err) {
    // aqui você já sabe que pelo menos uma falhou
    // log, retry, fallback, etc.
    return null;
  }
}

Promise.allSettled() + filtragem de resultados

async function carregarComRelatorio(promises) {
  const settled = await Promise.allSettled(promises);

  const ok = [];
  const erros = [];

  for (const item of settled) {
    if (item.status === 'fulfilled') ok.push(item.value);
    else erros.push(item.reason);
  }

  return { ok, erros };
}

Dica: allSettled é ótimo para não interromper operações. Depois, você decide o que fazer com os que falharam (ex.: tentar de novo só neles).


2) LocalStorage no navegador

O que é e para que serve

LocalStorage é um armazenamento simples do navegador para pares chave→valor (sempre strings) que persistem mesmo após fechar a aba ou o navegador (enquanto no mesmo domínio).

Use quando:

  • Precisa persistir preferências do usuário (tema, filtros, layout).
  • Quer guardar rascunhos (ex.: conteúdo de um formulário) localmente.
  • Mantém pequenos estados da interface sem servidor.

Observação: LocalStorage não é banco de dados. Ideal para poucos KBs até alguns MB. Evite dados sensíveis.

LocalStorage vs SessionStorage vs Cookies

RecursoPersiste após fechar navegador?Tamanho típicoEnvia ao servidor?Uso comum
LocalStorageSimAlguns MBNãoPreferências, rascunhos
SessionStorageNão (só durante a aba)Alguns MBNãoEstados temporários por aba
CookiesDepende da expiração~4KB por cookieSim, por padrãoSessões, flags do servidor

2.1) Operações básicas no LocalStorage (com exemplos)

Armazenar (create/update)

// Sempre armazene strings. Para objetos, use JSON.
const preferencia = { tema: 'dark', itensPorPagina: 20 };

// salvar
localStorage.setItem('preferencia', JSON.stringify(preferencia));

// atualizar (basta sobrescrever)
const atual = JSON.parse(localStorage.getItem('preferencia') || '{}');
atual.itensPorPagina = 50;
localStorage.setItem('preferencia', JSON.stringify(atual));

Explicação:

  • setItem(chave, valorString) salva/atualiza.
  • Para objetos, usamos JSON.stringify ao salvar e JSON.parse ao ler.

Ler (read)

const salvo = localStorage.getItem('preferencia'); // pode ser null
const obj = salvo ? JSON.parse(salvo) : null;
console.log(obj);

Remover e limpar

localStorage.removeItem('preferencia'); // remove só essa chave
localStorage.clear(); // limpa TUDO do domínio

Inspecionar chaves

for (let i = 0; i < localStorage.length; i++) {
  const key = localStorage.key(i);
  console.log(key);
}


2.2) Casos de uso práticos

  • Tema da interface (dark/light): salvar a escolha do usuário.
  • Filtros e paginação: manter a seleção ao voltar para a página.
  • Rascunho de formulário: evitar perder texto ao recarregar a página.
  • Passo a passo (wizards): lembrar o passo atual.

2.3) Persistindo dados de um formulário simples com LocalStorage

Cenário: formulário de newsletter. Queremos salvar automaticamente o que o usuário digitar e restaurar quando ele voltar.

HTML + JS (tudo junto, focado no essencial)

<form id="newsletter" style="display:grid; gap:12px; max-width:360px">
  <label>
    Nome
    <input id="nome" name="nome" type="text" placeholder="Seu nome" />
  </label>
  <label>
    E-mail
    <input id="email" name="email" type="email" placeholder="voce@exemplo.com" />
  </label>
  <label>
    Receber novidades?
    <input id="optin" name="optin" type="checkbox" />
  </label>

  <div style="display:flex; gap:8px">
    <button type="submit">Enviar</button>
    <button type="button" id="limpar">Limpar rascunho</button>
  </div>
</form>

<script>
  const FORM_KEY = 'newsletter_draft';

  // 1) Restaurar rascunho ao carregar
  document.addEventListener('DOMContentLoaded', () => {
    const salvo = localStorage.getItem(FORM_KEY);
    if (salvo) {
      try {
        const data = JSON.parse(salvo);
        document.getElementById('nome').value = data.nome ?? '';
        document.getElementById('email').value = data.email ?? '';
        document.getElementById('optin').checked = !!data.optin;
      } catch (_) {
        // se der erro no parse, limpamos o rascunho inválido
        localStorage.removeItem(FORM_KEY);
      }
    }
  });

  // 2) Salvar rascunho a cada digitação/alteração
  const campos = ['nome', 'email', 'optin'];
  campos.forEach(id => {
    const el = document.getElementById(id);
    const evento = (el.type === 'checkbox') ? 'change' : 'input';
    el.addEventListener(evento, () => {
      const draft = {
        nome: document.getElementById('nome').value.trim(),
        email: document.getElementById('email').value.trim(),
        optin: document.getElementById('optin').checked
      };
      localStorage.setItem(FORM_KEY, JSON.stringify(draft));
    });
  });

  // 3) Submit: envia e limpa rascunho
  document.getElementById('newsletter').addEventListener('submit', (e) => {
    e.preventDefault();

    // Aqui você faria o fetch para sua API
    // fetch('/api/newsletter', { method:'POST', body: ... })

    alert('Formulário enviado! (simulado)');
    localStorage.removeItem(FORM_KEY);
    e.target.reset();
  });

  // 4) Botão "Limpar rascunho"
  document.getElementById('limpar').addEventListener('click', () => {
    localStorage.removeItem(FORM_KEY);
    document.getElementById('newsletter').reset();
  });
</script>

Explicação linha a linha (resumo):

  • FORM_KEY define a chave única no LocalStorage.
  • DOMContentLoaded restaura o rascunho salvo (se existir) e preenche os campos.
  • Em cada mudança de campo, serializamos o estado do formulário em JSON e salvamos.
  • Ao enviar, simulamos um post, limpamos o rascunho e resetamos o formulário.
  • O botão “Limpar rascunho” permite remover manualmente o conteúdo salvo.

Bônus (opcional): você pode ouvir o evento storage para sincronizar o rascunho entre duas abas abertas do mesmo site:

window.addEventListener('storage', (e) => {
  if (e.key === 'newsletter_draft') {
    // Atualize a UI da aba atual com o novo valor e.newValue
  }
});


3) Dicas e armadilhas comuns

Promises

  • Dispare em paralelo quando as tarefas não dependem uma da outra.
  • Cheque status HTTP antes de usar res.json().
  • all para tudo ou nada; allSettled para relatório completo.
  • Log e monitoramento: registre erros (ex.: console.error, Sentry, etc.).
  • Evite “await dentro de for” quando dá para mapear e usar Promise.all — você ganha em performance.

LocalStorage

  • Só strings: sempre use JSON.stringify/parse para objetos.
  • Limite de tamanho: guarde apenas o necessário (preferências, rascunhos).
  • Síncrono: operações bloqueiam a thread principal; evite escrever em loop pesado.
  • Dados sensíveis: não salve senhas, tokens ou informações pessoais sensíveis.
  • Chaves únicas: padronize (ex.: app:area:recurso) para organizar.

Conclusão

Chegamos ao fim da aula . Se você acompanhou até aqui, já tem uma boa base para entender como usar Promise.all() e Promise.allSettled() para lidar com várias tarefas assíncronas, além de saber usar o LocalStorage para guardar informações de forma prática no navegador.

Esses recursos podem parecer detalhes, mas fazem uma grande diferença no dia a dia. Já me ajudaram a deixar meus projetos mais organizados e a dar uma cara mais “profissional” ao que eu estava construindo.

Agora é a sua vez: pratique os exemplos, brinque com os códigos, invente seus próprios casos de uso. Quanto mais você experimentar, mais natural vai ficar. Na proxima aula ja vou trazer um projeto usando LocalStorage e uso de api

🔗Link da aula no GITHUB: https://github.com/guilherme-silvam/js-aulas-guilhermemachadodev/tree/main/aula-19-guilhermemachadodev

Publicar comentário