JavaScript assíncrono

Aprenda como JavaScript lida com tarefas demoradas sem congelar a página. Acompanhe o laço de eventos para prever a ordem de execução do código, agende tarefas com temporizadores e escreva código assíncrono nos três estilos: funções de retorno, promessas e funções assíncronas com await. Trate falhas com catch, try e finally e execute tarefas independentes ao mesmo tempo com Promise.all e os outros combinadores de promessas.

BásicoAula 7 de 9 do curso Curso de Programação em JavaScript

O que você vai aprender

  1. Distinguir código síncrono de código assíncrono, acompanhar o laço de eventos pela pilha de chamadas e suas filas e agendar tarefas com temporizadores.
    • Distinguir código síncrono de código assíncrono
      Explicar por que JavaScript executa um trecho do seu código por vez, por que código demorado congela uma página e como operações assíncronas permitem que um programa continue trabalhando enquanto espera.
    • Acompanhar o laço de eventos
      Rastrear como a pilha de chamadas, a fila de tarefas e a fila de microtarefas determinam a ordem de execução do código e prever a saída de código que mistura temporizadores e promessas.
    • Agendar a execução de código com temporizadores
      Executar código uma vez após um atraso ou repetidamente com setTimeout e setInterval, cancelar temporizadores e explicar por que o atraso de um temporizador é uma espera mínima, e não um tempo exato.
  2. Passar funções para operações assíncronas, informar erros pela convenção de erro no primeiro argumento e reconhecer por que funções de retorno muito aninhadas são difíceis de ler e manter.
    • Passar funções de retorno para operações assíncronas
      Escrever funções que aceitam uma função de retorno, distinguir funções de retorno síncronas de assíncronas e levar um resultado assíncrono ao código que precisa dele.
    • Tratar erros em funções de retorno e evitar seu aninhamento excessivo
      Usar a convenção de erro no primeiro argumento, explicar por que try e catch não conseguem capturar um erro lançado posteriormente em uma função de retorno e reduzir o aninhamento excessivo desse código.
  3. Criar promessas, responder aos seus resultados com then, catch e finally e encadear etapas assíncronas em uma sequência legível.
    • Explicar os estados de uma promessa e criar uma promessa
      Descrever os estados em que uma promessa pode estar, criar promessas com o construtor Promise e encapsular um temporizador em uma promessa.
    • Responder aos resultados de promessas com then, catch e finally
      Registrar tratadores executados quando uma promessa é cumprida, rejeitada ou concluída e usá-los para atualizar uma página no momento certo.
    • Encadear promessas em uma sequência
      Retornar valores e promessas dos tratadores para construir cadeias sem aninhamento de etapas dependentes e evitar erros de ausência de return e de aninhamento.
  4. Escrever funções assíncronas, pausá-las com await até que promessas sejam concluídas, reescrever cadeias de promessas e APIs baseadas em funções de retorno nesse estilo e evitar erros comuns de código assíncrono.
    • Escrever funções assíncronas e usar await
      Declarar funções assíncronas, explicar o que elas retornam e usar await para pausar uma função até que uma promessa seja concluída sem bloquear a página.
    • Reescrever cadeias de promessas e APIs baseadas em funções de retorno com async e await
      Transformar cadeias de promessas em funções assíncronas, encapsular funções baseadas em funções de retorno em promessas para que possam ser aguardadas e escolher um estilo claro.
    • Evitar erros comuns com async e await
      Reconhecer e corrigir omissões de await, funções de retorno assíncronas em forEach, promessas usadas como condições e chamadas assíncronas cujos erros não são tratados.
  5. Capturar e recuperar-se de falhas em cadeias de promessas e com try e catch, realizar a limpeza com finally e garantir que falhas nunca passem despercebidas.
    • Tratar rejeições em cadeias de promessas
      Acompanhar como uma rejeição percorre uma cadeia de promessas, recuperar-se dela ou encaminhá-la e posicionar tratadores catch onde possam tratar as falhas certas.
    • Usar try, catch e finally com await
      Usar objetos Error e a instrução try, catch e finally para tratar falhas de promessas aguardadas, fazer a limpeza de forma confiável e fornecer alternativas de contingência úteis.
    • Manter as falhas visíveis e recuperar-se de forma controlada
      Evitar rejeições não tratadas e a supressão de erros, acrescentar contexto aos erros e escolher entre tentar novamente, usar uma alternativa de contingência e informar o usuário.
  6. Iniciar tarefas independentes ao mesmo tempo, aguardá-las juntas e escolher o combinador de promessas adequado para situações de tudo ou nada, aproveitamento de resultados parciais, primeira conclusão e primeiro sucesso.
    • Iniciar tarefas em conjunto e aguardar com Promise.all
      Executar tarefas assíncronas independentes ao mesmo tempo, combinar seus resultados com Promise.all e explicar seu tempo de execução e comportamento em caso de falha.
    • Escolher entre Promise.allSettled, Promise.race e Promise.any
      Usar allSettled quando cada resultado importa, race para a primeira tarefa a ser concluída, como em um limite de tempo, e any para o primeiro sucesso, e comparar os quatro combinadores.

Guia de estudo de JavaScript assíncrono

6 capítulos

1

Explicar como JavaScript executa código assíncrono

JavaScript frequentemente precisa esperar enquanto uma pessoa clica ou um temporizador faz a contagem. Durante essa espera, a página deve continuar respondendo. Forme uma imagem do que é executado agora, do que espera no ambiente e do que entra em uma fila para depois. Em seguida, use essa imagem para rastrear a saída do console e agendar temporizadores. O ponto principal é acompanhar quando uma função é chamada, não apenas onde seu nome aparece no código.

1.1 Distinguir código síncrono de código assíncrono

O código síncrono termina a instrução atual antes de passar para a próxima. O JavaScript da sua página é executado em uma única thread principal, portanto dois trechos desse código não são executados no mesmo instante. Isso é fácil de acompanhar até que uma instrução inicie um trabalho cujo resultado chegará depois.

Começar agora, tratar o resultado depois

Uma operação assíncrona começa agora e entrega seu resultado depois, por uma função de retorno ou uma promessa. O navegador ou o Node.js é o ambiente hospedeiro. Ele gerencia a espera enquanto seu programa continua. Temporizadores, entradas do usuário e carregamento de imagens usam esse tipo de organização.

console.log("Opening");
setTimeout(() => console.log("Ready"), 700);
console.log("Waiting");

Leia a chamada a setTimeout como “registre esta função”. Ela retorna sem esperar 700 ms. O script atual, portanto, registra “Opening” e “Waiting” primeiro. Após cerca de 700 ms, a função de retorno pode ser executada e registrar “Ready”. O atraso exato também depende de quando a thread principal fica livre.

Há duas atividades diferentes aqui: o ambiente acompanha o temporizador, e JavaScript acaba chamando a função que registra o resultado. A espera se sobrepõe a outros trabalhos; a função de retorno ainda é executada na thread principal.

Por que uma página congela

Um laço síncrono demorado mantém a thread ocupada. Enquanto ele é executado, tratadores de clique e funções de retorno de temporizadores não podem ser executados, e a página não consegue atualizar sua exibição por essa thread. Registrar um temporizador com atraso zero não lhe dá permissão para interromper o laço.

Suponha que um temporizador fique pronto enquanto um cálculo ainda está em execução. O ambiente coloca sua função de retorno na fila. Terminar o cálculo é o que libera a thread; o prazo do temporizador, por si só, não consegue fazer isso. Uma função de retorno que realiza outro cálculo demorado também pode congelar a página.

Para reduzir o efeito de um cálculo sobre a capacidade de resposta, divida-o em partes menores que permitam outras tarefas entre elas ou transfira o trabalho adequado para um web worker, uma thread separada em segundo plano fornecida pelo navegador. Simplesmente colocar um laço grande dentro de uma função assíncrona mantém esse laço na thread principal.

Três maneiras de receber um resultado

Uma função de retorno passa a próxima ação como uma função. Uma promessa representa um resultado eventual e permite associar tratadores de sucesso e de falha. Uma função assíncrona trabalha com promessas, enquanto await permite pausar sua própria execução em um ponto claro.

Esses estilos coexistem. Um temporizador ainda recebe uma função de retorno mesmo quando o código ao redor usa promessas. Uma função assíncrona pode esperar por uma promessa produzida por uma função envoltória baseada em função de retorno. Escolha o estilo que torne o caminho do resultado fácil de acompanhar.

Ao ler código assíncrono, marque primeiro as instruções executadas durante a chamada atual. Em seguida, marque as funções registradas para depois. Uma linha dentro de uma função de retorno registrada não é executada apenas porque a execução chegou à linha que cria essa função.

Principais conclusões

  • JavaScript síncrono ocupa a thread principal até que o trabalho atual termine.
  • O ambiente hospedeiro gerencia a espera e agenda o código que trata o resultado.
  • Funções de retorno assíncronas são executadas depois na thread principal.
  • Uma função assíncrona ainda pode bloquear a página com um cálculo demorado.

1.2 Acompanhar o laço de eventos

O laço de eventos coordena o trabalho pronto para execução. Para prever a saída, mantenha três coisas separadas: as funções em execução agora, as funções de retorno esperando como tarefas e o trabalho relacionado a promessas esperando como microtarefas.

Acompanhe a pilha e as filas

A pilha de chamadas mantém as chamadas de função ativas. Chamar uma função adiciona um quadro a ela; retornar remove esse quadro. A função atual é executada até retornar ou chamar outra função.

Uma tarefa pode ser um script, uma função de retorno de temporizador ou um tratador de clique. As tarefas são executadas uma por vez. Os navegadores podem ter várias filas de tarefas, portanto não suponha que todas as fontes de trabalho compartilhem uma única fila universal de primeira entrada, primeira saída.

A fila de microtarefas contém tratadores de promessas de then, catch e finally, continuações após await e funções de retorno passadas para queueMicrotask. Em um ponto de verificação de microtarefas após o trabalho atual, a fila é esvaziada antes da próxima tarefa. Microtarefas adicionadas durante esse esvaziamento também são executadas antes da próxima tarefa.

Rastreie um script
console.log("begin");
setTimeout(() => console.log("timer"), 0);
Promise.resolve().then(() => console.log("promise"));
console.log("finish");
  1. O script registra “begin”.
  2. Ele registra uma função de retorno de temporizador; não a chama.
  3. Ele coloca o tratador da promessa cumprida na fila como uma microtarefa.
  4. Ele registra “finish” e termina o trabalho atual.
  5. A microtarefa registra “promise”.
  6. A tarefa do temporizador registra “timer”.

Uma promessa já cumprida ainda agenda seu tratador para depois. Um atraso de 0 ms ainda agenda uma tarefa de temporizador.

Inclua trabalhos adicionados durante o processo
Promise.resolve()
  .then(() => console.log("first"))
  .then(() => console.log("third"));
queueMicrotask(() => console.log("second"));
console.log("now");

O script registra “now”. A fila de microtarefas contém inicialmente o primeiro tratador de promessa e depois a função de retorno de queueMicrotask. Quando o primeiro tratador registra “first” e retorna, ele deixa o próximo tratador de promessa pronto. Esse novo tratador entra no final da fila. O restante da saída é “second” e depois “third”.

Conclusão e capacidade de resposta

Quando uma tarefa ou microtarefa começa, o trabalho colocado na fila de forma assíncrona não pode interrompê-la. Esse comportamento de execução até a conclusão permite raciocinar sobre uma alteração local sem que um temporizador entre repentinamente no meio da função.

Após o término das microtarefas, o navegador pode atualizar a tela em uma oportunidade de renderização. Ele não precisa redesenhar a tela após cada tarefa. Um fluxo infinito de microtarefas pode impedir tanto as tarefas posteriores quanto a renderização: cada nova microtarefa mantém o esvaziamento atual em andamento. Adiar o trabalho para uma microtarefa, portanto, não garante que a página tenha uma chance de responder.

Para fazer um rastreamento, escreva primeiro a saída imediata, esvazie as microtarefas em ordem e só então passe para as tarefas de temporizador prontas. Adicione o trabalho recém-agendado à fila apropriada quando o código de fato o agendar.

Acompanhe um temporizador criado por uma microtarefa
setTimeout(() => console.log("outer timer"), 0);
Promise.resolve().then(() => {
  console.log("job one");
  setTimeout(() => console.log("inner timer"), 0);
}).then(() => console.log("job two"));
console.log("script");

O script registra o temporizador externo e o primeiro tratador de promessa e depois registra “script”. A primeira microtarefa registra “job one” e registra o temporizador interno. O retorno desse tratador coloca “job two” na fila, que também é executado antes de uma tarefa de temporizador. A saída termina com “outer timer” e depois “inner timer”: os dois temporizadores têm o mesmo atraso, e o externo foi registrado primeiro.

Criar um temporizador durante uma microtarefa não transforma sua função de retorno em uma microtarefa. Classifique o trabalho pela API de agendamento que o recebe e depois adicione-o à fila apropriada.

Principais conclusões

  • A pilha de chamadas registra as chamadas ativas.
  • Tratadores de promessas e continuações de await são executados como microtarefas.
  • Esvazie as microtarefas recém-adicionadas antes de pegar outra tarefa.
  • Funções de retorno na fila não podem interromper o JavaScript em execução.
  • A renderização é possível após as microtarefas, mas não é garantida após cada tarefa.

1.3 Agendar a execução de código com temporizadores

Temporizadores agendam funções de retorno para depois. Eles medem um atraso em milissegundos: 1000 ms é 1 s. O atraso determina quando uma função de retorno pode ficar apta a ser executada; outros trabalhos e limites do navegador podem fazer com que ela seja executada mais tarde.

Agende e cancele uma chamada
function remind(topic) {
  console.log("Review " + topic);
}
const reminder = setTimeout(remind, 2000, "promises");
// To cancel before it runs:
clearTimeout(reminder);

Argumentos adicionais após o atraso são passados para a função de retorno. A ausência de atraso equivale a 0. Guarde o identificador de temporizador retornado se talvez precise cancelá-lo. Os navegadores retornam um identificador numérico; o Node.js retorna um objeto Timeout. Ambos funcionam com clearTimeout. Cancelar um temporizador concluído ou inexistente não tem efeito.

Passe uma função: setTimeout(remind, 2000, "promises"). Escrever setTimeout(remind("promises"), 2000) chama remind imediatamente e passa seu valor de retorno para o temporizador. Uma função de seta é outra maneira de adiar uma chamada com argumentos: setTimeout(() => remind("promises"), 2000).

Repita com um temporizador periódico ou outro temporizador de execução única
let ticks = 0;
const clock = setInterval(() => {
  ticks++;
  console.log("Tick " + ticks);
  if (ticks === 3) clearInterval(clock);
}, 1000);

O primeiro disparo ocorre após cerca de um segundo. A terceira função de retorno interrompe os disparos futuros. Guarde o identificador do temporizador periódico e use clearInterval quando o trabalho repetido precisar terminar.

Uma função de retorno pode, em vez disso, agendar seu próximo temporizador de execução única. Quando ela chama setTimeout ao final de seu trabalho síncrono, o próximo atraso começa depois desse trabalho. Isso proporciona uma pausa entre as execuções e permite um atraso diferente a cada vez. Um temporizador periódico continua solicitando execuções em um ritmo fixo; um trabalho lento pode deixar pouca pausa. Se uma execução iniciar trabalho assíncrono, espere também por esse trabalho antes de agendar a próxima execução quando a sobreposição for um problema.

O tempo tem limites

Uma thread ocupada atrasa funções de retorno prontas. Os navegadores também reduzem a frequência dos temporizadores em abas em segundo plano, muitas vezes para cerca de uma verificação por segundo ou menos. Quando o nível de aninhamento dos temporizadores é maior que cinco, a regra de temporizadores do HTML eleva atrasos abaixo de 4 ms para 4 ms. Atrasos muito grandes também exigem cuidado: o limite usual de 32 bits com sinal é 2147483647 ms, cerca de 24,8 dias; valores acima disso podem causar estouro e disparar muito antes.

Capture o valor correto do laço
for (let i = 1; i <= 3; i++) {
  setTimeout(() => console.log(i), 100);
}

Cada iteração com let tem sua própria vinculação, portanto a saída é “1”, “2”, “3”. Com var, todas as funções de retorno compartilham a mesma variável. Após o laço, ela vale 4, portanto cada uma registra “4”.

Espere por um período sem eventos

O adiamento até cessarem os eventos cancela um temporizador de execução única pendente sempre que um novo evento chega. Um novo temporizador então agenda a ação para depois do evento mais recente. Com um atraso de 300 ms, digitar a cada 100 ms continua adiando a ação; ela é executada cerca de 300 ms após o fim da digitação. Armazene o identificador fora do tratador para que a próxima chamada possa cancelar o temporizador anterior.

Construa um tratador de adiamento
let pendingSave;
function scheduleSave(text) {
  clearTimeout(pendingSave);
  pendingSave = setTimeout(() => {
    console.log("Saved: " + text);
  }, 300);
}
scheduleSave("n");
scheduleSave("no");
scheduleSave("note");

Essas chamadas acontecem no mesmo script. Cada uma cancela o identificador anterior antes de substituí-lo, portanto apenas “Saved: note” é registrado, após cerca de 300 ms. A função de retorno final captura o parâmetro de texto dessa chamada. O identificador fica fora da função para que cada chamada possa cancelar o temporizador de execução única agendado anteriormente.

Se o identificador fosse declarado novamente dentro de cada chamada, a chamada seguinte não manteria o identificador do temporizador anterior. Se um tratador agendasse sem cancelar, ele entregaria as três mensagens. O adiamento exige tanto o identificador compartilhado quanto o cancelamento antes da substituição.

Principais conclusões

  • Passe uma função de retorno e meça atrasos em milissegundos.
  • Use o identificador retornado para cancelar um temporizador.
  • Atrasos de temporizadores não garantem tempos exatos de execução.
  • Um temporizador periódico se repete até ser cancelado.
  • Use let para criar vinculações separadas no laço.
  • O adiamento até cessarem os eventos reinicia um atraso após cada evento.
2

Escrever e gerenciar código baseado em funções de retorno

Uma função de retorno permite que quem chama forneça o próximo trabalho como uma função. Essa ideia simples sustenta temporizadores, eventos e muitas APIs mais antigas. Aqui você escreverá uma pequena operação baseada em função de retorno, acompanhará o resultado até o código que precisa dele e encaminhará falhas por um parâmetro de erro. Você também verá como etapas dependentes criam código aninhado e por que o tratamento consistente de erros importa em cada nível.

2.1 Passar funções de retorno para operações assíncronas

Uma função de retorno é uma função que você passa para outra função para ser chamada por ela. O código da função de retorno descreve o que deve acontecer com um resultado. Sua execução durante a chamada atual ou depois depende da API que a recebe.

Uma função de retorno pode ser executada de forma síncrona
const doubled = [2, 5].map(n => n * 2);
console.log(doubled);

map chama sua função de retorno para cada elemento antes de retornar o novo vetor. O resultado é [4, 10]. A função de seta é uma função de retorno, mas esse código não agenda trabalho futuro. Você pode usar doubled na próxima linha porque o vetor já está completo.

Entregue um resultado posterior
function prepareLabel(name, callback) {
  setTimeout(() => {
    callback("Label: " + name);
  }, 200);
}
prepareLabel("Mira", label => {
  console.log(label);
});
console.log("Label requested");

prepareLabel inicia um temporizador e retorna. O script registra “Label requested”. Após cerca de 200 ms, a função de retorno do temporizador constrói o rótulo e chama a função de retorno fornecida, que registra “Label: Mira”. A operação escolhe quando chamar; quem chama escolhe o que fazer com o resultado.

Coloque o trabalho dependente onde o resultado existe
let label;
prepareLabel("Mira", value => {
  label = value;
});
console.log(label);

Isso registra undefined: a atribuição ainda não aconteceu. Mova console.log(value) para dentro da função de retorno. Acrescentar um temporizador mais longo e sem relação antes de ler label seria apenas uma suposição sobre quando o resultado estará disponível; usar a função de retorno do resultado vincula o trabalho à conclusão real.

Um return dentro de uma função de retorno adiada também não pode retornar o valor da função externa. A chamada externa já terminou. No exemplo, return "Label: Mira" dentro da função de retorno do temporizador retornaria para o mecanismo do temporizador, deixando quem chamou prepareLabel sem o rótulo posterior.

Passe a próxima ação sem chamá-la

Uma função de retorno nomeada também funciona:

function showLabel(label) {
  console.log(label);
}
prepareLabel("Mira", showLabel);

showLabel é o valor da função. showLabel() a chamaria imediatamente sem rótulo e passaria seu valor de retorno. Use uma função envoltória de seta quando a ação posterior precisar de um argumento específico ou de várias instruções.

Para duas etapas assíncronas dependentes, inicie a segunda dentro da função de retorno da primeira etapa. Por exemplo, após receber um usuário, passe esse usuário para uma operação que prepara suas configurações. Iniciar ambas fora de suas funções de retorno não estabeleceria essa dependência.

A função que recebe a função de retorno controla quantas vezes a chama. Algumas funções de retorno, como tratadores de eventos, são chamadas muitas vezes de propósito. Uma operação com um único resultado deve chamar sua função de retorno de resultado uma vez. Uma promessa dá a essa operação um resultado que só pode ser concluído uma vez.

Principais conclusões

  • O momento de execução de uma função de retorno depende da API que a chama.
  • map usa funções de retorno síncronas; uma função de retorno de temporizador é executada depois.
  • Use um resultado posterior dentro de sua função de retorno ou do código chamado por ela.
  • Retornar de uma função de retorno não significa retornar de sua função externa.

2.2 Tratar erros em funções de retorno e evitar seu aninhamento excessivo

Uma operação assíncrona pode falhar ou ter sucesso. Uma API baseada em função de retorno precisa de uma maneira clara de informar cada resultado e de um lugar claro para quem chama tratá-lo.

Verifique o erro primeiro

A convenção de erro no primeiro argumento usa callback(err, result). Uma chamada bem-sucedida passa null para o erro. Uma chamada que falha passa um objeto Error e não fornece um resultado utilizável.

function readLabel(id, callback) {
  setTimeout(() => {
    if (id <= 0) {
      callback(new Error("Invalid label id"));
      return;
    }
    callback(null, { id: id, text: "Draft" });
  }, 200);
}
readLabel(4, (err, label) => {
  if (err) {
    console.log(err.message);
    return;
  }
  console.log(label.text);
});

O produtor para após informar um identificador inválido, portanto não pode também informar sucesso. O consumidor para após identificar um erro, portanto não pode tentar ler label.text de um resultado ausente. Com o identificador 4, a saída é “Draft”. Com o identificador 0, é “Invalid label id”.

Ambos os retornos importam. Sem o retorno do consumidor, um segundo erro sobre um valor indefinido poderia ocultar a mensagem original útil. Sem o retorno do produtor, uma operação poderia chamar a função de retorno duas vezes. Para uma API com um único resultado, informe exatamente um desfecho.

Um try externo já terminou
try {
  setTimeout(() => {
    throw new Error("Late failure");
  }, 0);
} catch (err) {
  console.log("Handled");
}

O try agenda o temporizador e termina. A função de retorno lança o erro em uma tarefa posterior, quando aquele try não está mais ativo, portanto “Handled” não é registrado. Capture um possível erro lançado dentro da função de retorno ou encaminhe uma falha pelo parâmetro de erro da API. Uma função envoltória baseada em promessa posterior deve transformar explicitamente esse erro da função de retorno em uma rejeição.

Mantenha as etapas dependentes legíveis

O código baseado em funções de retorno se torna difícil de acompanhar quando cada próxima etapa aparece dentro da função de retorno da etapa anterior. Ler um perfil, depois seus projetos e depois as tarefas de um projeto cria vários níveis de aninhamento. Cada operação também precisa de sua própria verificação de erro. Esse padrão costuma ser chamado de inferno de funções de retorno ou pirâmide da perdição.

Funções nomeadas podem dar nomes significativos a essas etapas e reduzir o aninhamento visual. Mantenha o tratamento de erros consistente entre elas: uma etapa posterior pode falhar tanto quanto a primeira. Promessas e funções assíncronas oferecem uma maneira menos aninhada de expressar a mesma sequência, mantendo a propagação de falhas conectada.

Nem todas as APIs baseadas em funções de retorno usam um parâmetro de erro. Alguns objetos do navegador têm eventos separados para sucesso e falha. Para uma imagem, defina os tratadores onload e onerror antes de atribuir src, para que ambos os resultados tenham um tratador quando o carregamento começar. Leia o contrato da API antes de supor que existe um parâmetro chamado err.

Principais conclusões

  • Uma função de retorno com erro no primeiro argumento recebe err antes do resultado.
  • Retorne após informar ou tratar um erro.
  • Operações com um único resultado devem chamar sua função de retorno exatamente uma vez.
  • Um try externo não pode capturar um erro lançado posteriormente por uma função de retorno.
  • Etapas nomeadas ou código baseado em promessas reduzem o aninhamento excessivo.
3

Criar e usar promessas

Uma promessa dá a uma operação um objeto compartilhado para seu resultado eventual. Ela torna explícitos o sucesso e a falha e permite conectar etapas dependentes sem aninhar cada uma na anterior. Comece construindo uma promessa em torno de um temporizador. Depois, associe tratadores, acompanhe as novas promessas que eles retornam e verifique se cada etapa assíncrona permanece conectada à cadeia que depende dela.

3.1 Explicar os estados de uma promessa e criar uma promessa

Uma promessa representa um resultado eventual. Uma função pode retorná-la imediatamente, permitindo que quem chama conecte trabalho a um resultado que ainda não existe.

Estados e conclusão

Uma promessa fica pendente até ser cumprida com um valor ou rejeitada com um motivo. Promessas cumpridas e rejeitadas estão concluídas; seu resultado não pode mudar.

“Resolvida” tem um sentido mais preciso do que um sinônimo cotidiano de “bem-sucedida”. Uma promessa resolvida com outra promessa pendente segue o resultado eventual dessa promessa, incluindo a rejeição. Portanto, ela pode estar resolvida e ainda pendente.

Crie uma promessa em torno de uma operação
function wait(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  });
}
wait(200).then(() => console.log("Pause finished"));

new Promise chama sua função executora imediatamente. Essa função registra um temporizador. Quando o temporizador chama resolve, a promessa é cumprida com undefined, e o tratador then registrado é executado depois como uma microtarefa. Essa função auxiliar fornece uma pausa reutilizável sem bloquear a thread.

Uma função executora pode receber tanto resolve quanto reject:

function reserve(quantity) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (quantity <= 8) resolve(8 - quantity);
      else reject(new Error("Not enough stock"));
    }, 200);
  });
}
reserve(3).then(left => console.log(left));
reserve(9).catch(err => console.log(err.message));

Para essas quantidades positivas, a primeira chamada é cumprida com 5 e a segunda é rejeitada com “Not enough stock”. A promessa permite que quem chama receba qualquer um dos resultados por meio do mesmo objeto retornado.

A criação inicia a função executora
console.log("before");
const result = new Promise(resolve => {
  console.log("creating");
  resolve(12);
});
console.log("after");
result.then(value => console.log(value));

A saída é “before”, “creating”, “after”, “12”. Associar then não inicia essa função executora. O construtor já a executou; then registra o código para consumir seu resultado.

Somente a primeira chamada a resolve ou reject decide o destino da promessa. Chamadas posteriores não podem substituí-lo. Isso também se aplica quando a primeira resolução adota outra promessa cujo resultado ainda está pendente. Chamar resolve não interrompe o código comum da função executora, portanto use return quando um ramo precisar terminar.

Faça cada caminho terminar corretamente

Um erro lançado de forma síncrona na função executora rejeita a promessa automaticamente. Um erro lançado em uma função de retorno posterior de temporizador está fora dessa chamada e não é convertido em rejeição. Use reject(new Error("...")) nesse caso; caso contrário, o erro pode não ser capturado enquanto a promessa permanece pendente.

Um ramo que não chama nenhuma das duas funções pode deixar quem chamou esperando para sempre. Use objetos Error como motivos de rejeição para que os tratadores possam ler uma mensagem e as ferramentas de depuração possam mostrar informações úteis sobre o erro.

Promise.resolve(value) cria uma promessa cumprida para um valor comum ou adota o resultado de outra promessa. Promise.reject(error) cria uma promessa rejeitada. Associe um tratamento de rejeição ao usar o segundo método.

Acompanhe um resultado adotado
const inner = new Promise(resolve => {
  setTimeout(() => resolve("packed"), 250);
});
const outer = new Promise((resolve, reject) => {
  resolve(inner);
  reject(new Error("Too late to replace it"));
});
outer.then(value => console.log(value));

A função executora interna inicia seu temporizador durante a construção. A função executora externa então resolve com inner, vinculando outer ao resultado eventual de inner. A chamada posterior a reject não pode substituir essa decisão. Outer permanece pendente até que inner seja cumprida, e seu tratador registra “packed” após cerca de 250 ms.

Se inner fosse rejeitada, outer seguiria essa rejeição. A primeira chamada a resolve selecionou qual resultado acompanhar; ela não garantiu sucesso. Essa distinção importa quando uma função envoltória repassa outra promessa em vez de fornecer um valor comum já disponível.

Principais conclusões

  • Promessas pendentes tornam-se cumpridas ou rejeitadas; resultados concluídos permanecem fixos.
  • A resolução pode adotar o resultado eventual de outra promessa.
  • A função executora é executada de forma síncrona durante a construção.
  • Use resolve e reject para conectar funções de retorno posteriores à promessa.
  • Um erro lançado posteriormente por uma função de retorno não rejeita automaticamente a promessa que a envolve.

3.2 Responder aos resultados de promessas com then, catch e finally

Tratadores de promessas conectam código ao sucesso, à falha ou a qualquer um dos resultados. Cada registro de tratador também retorna uma nova promessa, permitindo que o próximo trecho de código acompanhe o que esse tratador produz.

Use o tratador adequado ao resultado

p.then(onFulfilled) recebe um valor de cumprimento. p.catch(onRejected) recebe um motivo de rejeição; é uma forma abreviada de p.then(undefined, onRejected). Os tratadores são funções e são executados como microtarefas, mesmo que p já tenha sido concluída.

Promise.resolve("draft")
  .then(text => console.log(text));
console.log("registered");

O script registra “registered” antes de o tratador registrar “draft”. Um valor disponível imediatamente não torna o tratador síncrono.

Passar uma função é essencial. then(console.log("draft")) registra “draft” durante o registro e passa undefined como tratador. Use then(() => console.log("draft")) quando a mensagem deve ser registrada depois. then(console.log) passa a própria função de registro, portanto ela recebe o valor.

Faça a limpeza após sucesso ou falha

finally é útil para ocultar um indicador de carregamento ou ativar um botão após a conclusão de uma operação. Sua função de retorno não recebe argumento de valor nem de erro.

Promise.resolve(4)
  .finally(() => console.log("Cleanup"))
  .then(value => console.log(value));

A saída é “Cleanup” e depois “4”. Retornar um valor comum da função de retorno de limpeza não substitui 4. Uma promessa retornada pode adiar a próxima etapa até que a limpeza termine. Se a limpeza lançar um erro ou retornar uma promessa rejeitada, essa falha se tornará o novo resultado da cadeia.

Um fluxo típico de página define um estado de carregamento antes de começar, trata um resultado em then, informa uma falha em catch e redefine o estado de carregamento em finally. Coloque o trabalho que depende do resultado em then ou catch, onde o argumento necessário está disponível.

Separe registros de tratadores de uma cadeia
const p = Promise.resolve(4);
p.then(value => value * 2);
p.then(value => console.log(value));

Esses são dois registros separados de tratadores em p. Ambos recebem 4. A multiplicação pertence à nova promessa retornada pelo primeiro then; ela não altera p nem alimenta o segundo registro.

Conecte-os para passar o valor transformado:

p.then(value => value * 2)
  .then(value => console.log(value));

Agora o tratador final recebe 8. A diferença está em qual promessa cada tratador é associado.

O objeto retornado ainda é uma promessa

Em const next = p.then(value => value * 2), next é uma nova promessa, não o número 8. O tratador pode ser executado depois que o script ao redor termina e pode, por sua vez, retornar trabalho que termina depois. Use outro tratador ou await para obter o valor. Tratar next imediatamente como um número perde essa informação sobre o momento de execução.

Principais conclusões

  • Use then para valores, catch para rejeições e finally para limpeza.
  • Passe funções como tratadores.
  • Cada then retorna uma nova promessa.
  • Tratadores separados em uma promessa recebem seu resultado original.
  • Uma limpeza bem-sucedida em finally preserva o resultado anterior; uma limpeza que falha o substitui.

3.3 Encadear promessas em uma sequência

Uma cadeia de promessas transforma a saída de um tratador na entrada do próximo. Cada elo deve retornar o valor ou a promessa de que os elos posteriores precisam.

Passe valores pela cadeia
Promise.resolve(3)
  .then(count => count * 4)
  .then(total => total + 2)
  .then(total => console.log(total));

O primeiro tratador recebe 3 e retorna 12. O segundo recebe 12 e retorna 14. O tratador final registra “14”. Uma expressão em uma função de seta retorna automaticamente; um corpo de função com chaves precisa de um return explícito para encaminhar um valor.

Retorne uma promessa para uma etapa dependente
function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
function makeCode() {
  return wait(100).then(() => "P4");
}
function makeLabel(code) {
  return wait(200).then(() => "Parcel " + code);
}
makeCode()
  .then(code => makeLabel(code))
  .then(label => console.log(label));

makeCode fornece “P4” após cerca de 100 ms. O próximo tratador retorna a promessa de makeLabel. A cadeia acompanha essa promessa, espera cerca de mais 200 ms e passa “Parcel P4” ao tratador final. Quem chamou recebe um rótulo, não uma promessa aninhada dentro do valor.

Encontre um elo quebrado

Compare esse tratador intermediário com este:

.then(code => {
  makeLabel(code);
})

Ele inicia makeLabel, mas não retorna nada. Sua nova promessa é cumprida com undefined sem esperar pelo rótulo. O próximo tratador, portanto, recebe undefined após a etapa do código, enquanto a preparação do rótulo continua separadamente. Erros desse trabalho separado também deixam de fluir por essa cadeia externa.

Corrija-o com return makeLabel(code); dentro das chaves ou remova as chaves para retornar a expressão. A mesma regra se aplica quando um tratador inicia uma pausa: .then(() => wait(200)) espera; .then(() => { wait(200); }) não espera.

Reduza o aninhamento e mantenha os valores necessários

Colocar outro then em um nível profundo dentro do primeiro tratador pode recriar o aninhamento típico de funções de retorno. Retorne a promessa interna e coloque o próximo tratador no nível externo. Isso preserva um único caminho para tempos de execução, valores e erros.

Cada tratador tem seu próprio escopo local. Se uma etapa posterior precisar tanto do código quanto do rótulo, retorne um objeto que contenha ambos:

makeCode()
  .then(code => makeLabel(code).then(label => ({ code, label })))
  .then(pair => console.log(pair.code + ": " + pair.label));

Esse pequeno tratador interno constrói o valor combinado e é retornado, portanto permanece conectado. Uma função assíncrona também pode manter os dois resultados em variáveis locais comuns.

Uma rejeição pula os tratadores de cumprimento posteriores até chegar a um tratador de rejeição. Um catch final pode tratar falhas de todas as etapas conectadas anteriores a ele. O trabalho dependente pertence a essa sequência; o trabalho independente pode começar junto com Promise.all.

Principais conclusões

  • Retorne o valor de que o próximo tratador precisa.
  • Retornar uma promessa faz a cadeia esperar por seu resultado.
  • Corpos de funções de seta com chaves precisam de return.
  • Mantenha o trabalho dependente e seus erros conectados à cadeia externa.
  • Encaminhe dados combinados quando etapas posteriores precisarem de valores locais anteriores.
4

Escrever código assíncrono com async e await

Funções assíncronas permitem escrever etapas baseadas em promessas na ordem em que você quer que elas aconteçam. Valores podem permanecer em variáveis nomeadas, e await marca os pontos em que a função pausa. Aprenda como essa pausa afeta quem chama, converta código mais antigo para esse estilo e identifique erros que deixam uma promessa onde é necessário um valor. O comportamento de laços merece atenção especial, pois uma função de retorno assíncrona não faz quem a chama esperar.

4.1 Escrever funções assíncronas e usar await

Uma função assíncrona sempre retorna uma promessa. Dentro dela, await pausa essa função até que um valor esteja disponível, permitindo ler as etapas dependentes de cima para baixo.

Retorne um valor por meio de uma promessa
async function getCount() {
  return 6;
}
getCount().then(count => console.log(count));

Chamar getCount() fornece uma promessa cumprida com 6. O tratador registra “6”. Se a função lançar um erro, a promessa retornada por ela será rejeitada. Retornar outra promessa faz a função assíncrona acompanhar o resultado eventual dessa promessa.

Você pode declarar uma função assíncrona com async function name(), uma função de seta assíncrona como const name = async () => {} ou um método assíncrono de objeto. O comportamento de retornar uma promessa é o mesmo.

Pause em await
function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
async function showSteps() {
  console.log("Step one");
  await wait(300);
  console.log("Step two");
}
showSteps();
console.log("Caller continues");

A saída começa com “Step one” e depois “Caller continues”. Cerca de 300 ms depois, ela registra “Step two”. Chamar a função executa seu corpo de forma síncrona até o primeiro await. A pausa devolve o controle a quem chamou. Quando a promessa é cumprida, o restante de showSteps continua por meio da fila de microtarefas.

Await pausa sua função, não a thread inteira. Outras tarefas prontas podem ser executadas durante a espera. Um cálculo síncrono grande antes ou depois do await ainda pode bloquear a thread enquanto esse cálculo é executado.

Leia um valor ou receba um erro

Uma promessa cumprida fornece seu valor: const count = await getCount(); define count como 6. Uma promessa rejeitada lança seu motivo na linha do await. Envolva a operação aguardada com try e catch quando essa função tiver que tratar a falha.

await também aceita valores comuns. Em const count = await 6;, o resultado continua sendo 6, mas a continuação da função é adiada. Ela não se torna síncrona apenas porque nenhuma operação de espera foi necessária.

Verifique onde await pode ser usado

Use await dentro da função assíncrona cujo corpo o contém. Ele também é permitido no nível superior de um módulo JavaScript. O corpo de uma função comum não pode conter await; uma função assíncrona externa não torna assíncrona uma função de retorno comum aninhada.

Quem chama ainda recebe uma promessa. Para usar o valor final, espere por ele em outra função assíncrona ou associe then. Por exemplo, const total = getCount(); armazena uma promessa; não armazena 6.

Um tratador de evento pode ser assíncrono. O tratador pode esperar por uma etapa de preparação antes de atualizar uma página. Dê a essa ação seu próprio tratamento de erros, pois o disparo do evento não espera pela promessa retornada pelo tratador. No início de um programa, uma chamada como showSteps().catch(console.error) dá a uma falha imprevista um tratador.

Principais conclusões

  • Uma função assíncrona retorna uma promessa para seu valor de retorno ou erro lançado.
  • O código antes do primeiro await é executado durante a chamada.
  • Await continua depois com um valor ou lança um motivo de rejeição.
  • Await deve estar dentro de uma função assíncrona ou no nível superior de um módulo.

4.2 Reescrever cadeias de promessas e APIs baseadas em funções de retorno com async e await

Uma cadeia de promessas pode se tornar uma função assíncrona sem alterar suas dependências. Cada resultado recebe uma variável local, e cada etapa aguardada começa somente depois que o resultado anterior de que ela precisa está pronto.

Substitua tratadores por atribuições com await
function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
function getUser(id) {
  return wait(100).then(() => ({ id, name: "Nia" }));
}
function getOrders(user) {
  return wait(200).then(() => [user.name + "-1", user.name + "-2"]);
}
async function showOrders(id) {
  const user = await getUser(id);
  const orders = await getOrders(user);
  console.log(user.name + ": " + orders.length + " orders");
}
showOrders(8).catch(console.error);

O primeiro await fornece um usuário. Só então getOrders(user) pode começar. Após cerca de 300 ms no total, a função registra “Nia: 2 orders”. Tanto user quanto orders estão no escopo dessa linha final. Em uma cadeia, o parâmetro de cada tratador pertenceria a um escopo local separado.

A cadeia correspondente retornaria getOrders(user) do primeiro tratador. Reescrevê-la com await torna essa espera explícita, mantendo a ordem. Um catch final da cadeia se torna um try/catch em torno das etapas aguardadas; finally se torna um bloco de limpeza.

Converta uma API baseada em função de retorno em uma API baseada em promessa

Adicionar async a uma função envoltória não faz o valor posterior de uma API baseada em função de retorno se tornar seu valor de retorno. Conecte a função de retorno à conclusão da promessa:

function readTag(id, callback) {
  setTimeout(() => {
    if (id <= 0) callback(new Error("Invalid tag"));
    else callback(null, "Tag " + id);
  }, 100);
}
function readTagAsync(id) {
  return new Promise((resolve, reject) => {
    readTag(id, (err, tag) => {
      if (err) {
        reject(err);
        return;
      }
      resolve(tag);
    });
  });
}

Dentro de uma função assíncrona, const tag = await readTagAsync(8); fornece “Tag 8”. Chamá-la com 0 em vez disso lança “Invalid tag” no await. O sucesso resolve a promessa da função envoltória; a falha a rejeita. O adaptador altera a forma como o resultado é entregue, não o que a operação subjacente faz.

Essa conversão é chamada de conversão para promessas. Para APIs comuns com um único resultado e erro no primeiro argumento, o Node.js também fornece util.promisify, e muitos módulos nativos já têm variantes baseadas em promessas, como fs/promises.

Mantenha um caminho claro

Temporizadores se tornam pausas por meio de uma função auxiliar wait que retorna uma promessa. Um laço assíncrono pode registrar uma etapa, esperar pela pausa e então começar a próxima iteração. Com funções de retorno, a próxima etapa teria que ser agendada dentro da função de retorno anterior.

Promessas, cadeias e funções assíncronas trabalham juntas: uma função assíncrona pode esperar por uma cadeia, e quem chama pode associar then à promessa retornada por uma função assíncrona. Dentro de uma função, use a forma que torne fáceis de acompanhar as dependências e o tratamento de erros. Um construtor Promise adicional é útil para adaptar uma função de retorno, mas operações existentes que retornam promessas podem ser aguardadas diretamente.

Principais conclusões

  • Cada etapa dependente de uma cadeia pode se tornar uma atribuição com await.
  • Variáveis locais mantêm resultados anteriores disponíveis mais tarde na função assíncrona.
  • Converta funções de retorno para promessas associando erros a reject e resultados a resolve.
  • APIs existentes baseadas em promessas podem ser aguardadas diretamente.

4.3 Evitar erros comuns com async e await

O código assíncrono pode parecer síncrono enquanto ainda passa promessas entre funções. Muitos erros surgem ao ler o valor eventual cedo demais ou ao supor que uma função auxiliar espera por funções de retorno que ela ignora.

Espere pelo valor antes de usá-lo
async function getQuantity() {
  return 18;
}
async function total() {
  const quantity = await getQuantity();
  console.log(quantity * 3);
}
total().catch(console.error);

Isso registra “54”. Sem await, quantity conteria uma promessa. Multiplicar essa promessa por 3 produz NaN, não um cálculo adiado que se atualiza automaticamente.

Uma condição tem a mesma armadilha. Uma verificação assíncrona que retorna false ainda retorna um objeto de promessa, e objetos são avaliados como verdadeiros. Use if (await allowed()) dentro de uma função assíncrona quando o ramo depender do booleano eventual. Testar if (allowed()) testa o próprio objeto.

forEach não coleta as promessas das funções de retorno
function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
async function labelAll(ids) {
  ids.forEach(async id => {
    await wait(100);
    console.log("Label " + id);
  });
  console.log("Labels finished");
}
labelAll([4, 9]).catch(console.error);

“Labels finished” aparece antes dos rótulos. forEach chama as duas funções de retorno, ignora as promessas retornadas por elas e termina sua própria iteração síncrona. Mesmo await ids.forEach(...) esperaria por undefined, não pelo trabalho das funções de retorno. Uma falha dentro dessas promessas ignoradas também está fora da promessa retornada por labelAll.

Para processamento em ordem, escreva um laço comum:

for (const id of ids) {
  await labelOne(id);
}
console.log("Labels finished");

Aqui labelOne é uma operação que retorna uma promessa. Cada item começa depois que o anterior termina. Para itens independentes, use await Promise.all(ids.map(id => labelOne(id))); antes do registro final. map coleta as promessas retornadas, e Promise.all fornece a quem chama uma promessa para a conclusão delas.

Separe independência de sintaxe

Esperar por cada operação imediatamente torna suas esperas sequenciais. Para esperas independentes de 1000, 2000 e 1500 ms, isso leva cerca de 4500 ms. Iniciar as três e combiná-las leva cerca de 2000 ms. Uma etapa que precisa de um resultado anterior ainda tem que esperar por ele.

Mantenha as falhas conectadas

Uma função aninhada precisa de sua própria palavra-chave async para usar await, mas essa palavra-chave, por si só, não faz quem a chama esperar pelo resultado. Verifique as duas pontas: a função de retorno retorna uma promessa, e quem chama a utiliza?

Para trabalho iniciado deliberadamente sem esperar pela conclusão, associe um tratador de rejeição, como backgroundTask().catch(console.error). Verifique também os retornos em funções envoltórias assíncronas: async function prepare() { await wait(100); } é cumprida com undefined. Esperar por uma etapa e retornar seu resultado são decisões separadas.

Retorne os resultados do grupo corrigido
function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
async function prepareItem(id) {
  await wait(50);
  return "Item " + id;
}
async function prepareAll(ids) {
  return await Promise.all(ids.map(id => prepareItem(id)));
}
prepareAll([6, 2]).then(items => console.log(items.join(", ")));

Cada função de retorno de map retorna uma promessa, e map mantém essas promessas em um vetor. All espera pelas duas e fornece ["Item 6", "Item 2"]. PrepareAll retorna esse vetor por meio de sua própria promessa, portanto quem chama registra “Item 6, Item 2” após o término do grupo.

Remover o return de prepareAll ainda faria a função esperar pelo grupo, mas quem chamou receberia undefined. Substituir map por forEach perderia o vetor de promessas. Verifique coleta, espera e retorno separadamente ao corrigir uma ação com vários itens.

Principais conclusões

  • Use await antes de usar um número ou booleano eventual.
  • forEach ignora promessas retornadas por funções de retorno assíncronas.
  • Use for...of para trabalho sequencial ou map com Promise.all para trabalho independente.
  • Uma função de retorno aninhada precisa de sua própria palavra-chave async.
  • Mantenha trabalho deliberadamente não aguardado associado a um tratador de rejeição.
5

Tratar erros em código assíncrono

Uma operação que falha faz parte do fluxo de controle do programa. O código deve decidir se vai se recuperar, tentar novamente, informar a falha ou passá-la a quem chamou e pode decidir. Acompanhe falhas em cadeias de promessas e em chamadas aguardadas e use código de limpeza executado em qualquer resultado. Por fim, coloque o tratamento de erros no limite de uma ação para que o usuário receba uma mensagem útil e você mantenha a causa técnica.

5.1 Tratar rejeições em cadeias de promessas

Uma rejeição de promessa segue pela cadeia conectada até que um tratador de rejeição lide com ela. Entender o que esse tratador retorna informa se a cadeia se recuperou ou continua em falha.

Acompanhe uma rejeição passando por tratadores de sucesso
Promise.resolve(8)
  .then(value => {
    throw new Error("Count rejected");
  })
  .then(value => console.log("Unused: " + value))
  .catch(err => {
    console.log(err.message);
    return 2;
  })
  .then(value => console.log("Using " + value));

O lançamento rejeita a nova promessa retornada pelo primeiro then. O próximo tratador de cumprimento é pulado, portanto “Unused” nunca é registrado. O catch registra “Count rejected” e retorna 2. Esse retorno cumpre o próximo elo, portanto o último tratador registra “Using 2”.

Uma promessa rejeitada explicitamente e um erro lançado dentro de um tratador entram no mesmo caminho de rejeição. Por isso, um catch final pode tratar falhas de várias etapas anteriores conectadas.

Escolha recuperação ou propagação

Um tratador de rejeição que retorna um valor se recupera com esse valor. Não retornar nada também recupera, mas com undefined. Registrar uma falha, portanto, não é o mesmo que manter a cadeia rejeitada:

Promise.reject(new Error("Label missing"))
  .catch(err => console.log(err.message))
  .then(value => console.log(value));

O tratador final recebe undefined. Se o código posterior exigir um rótulo real, essa recuperação silenciosa provavelmente está errada. Retorne uma alternativa de contingência utilizável ou lance o erro novamente.

function reportAndRethrow(err) {
  console.error(err);
  throw err;
}

Usar essa função em catch registra o erro e deixa a próxima promessa rejeitada com o mesmo motivo. Quem chama mais adiante na cadeia pode decidir o que fazer.

Coloque o tratador após o trabalho que ele protege

Um catch trata falhas anteriores a ele. Se ele se recuperar, as etapas posteriores serão executadas normalmente, e suas novas falhas precisarão de um tratador posterior. Um tratador no meio não pode capturar erros que ainda não aconteceram.

Também há uma diferença importante entre .then(success, failure) e .then(success).catch(failure). Os dois tratadores em um único then respondem ao resultado da promessa recebida. Seu tratador de falha não captura um erro lançado por seu próprio tratador de sucesso. Esse erro pertence à nova promessa retornada pela chamada, portanto é necessário um catch posterior.

Preserve erros úteis

Rejeite com objetos Error para que os tratadores possam ler err.message e err.name. Lançar ou rejeitar uma cadeia de caracteres é permitido, mas o código que espera os campos de um Error não os receberá. Mantenha o erro original ao propagar uma falha, a menos que um contexto adicional ajude quem chama a entender qual operação falhou.

Principais conclusões

  • Rejeições pulam tratadores de cumprimento até chegar a um tratador de rejeição.
  • Retornar de catch recupera; não retornar nada recupera com undefined.
  • Relance o erro para manter a falha seguindo pela cadeia.
  • Um catch protege as etapas conectadas anteriores a ele.
  • Um tratador de rejeição de then não pode capturar o erro lançado pelo tratador de sucesso do mesmo then.

5.2 Usar try, catch e finally com await

try, catch e finally permitem que uma função assíncrona trate falhas no ponto em que as espera. Use essa estrutura para manter juntos o tratamento de resultados, a recuperação e a limpeza.

Um erro transfere o controle

Um objeto Error descreve uma falha. new Error("Label missing") tem a mensagem “Label missing” e o nome “Error”; ferramentas de depuração também podem mostrar seu rastreamento de pilha. Falhas nativas têm tipos como TypeError.

throw interrompe a execução normal naquele ponto e transfere o controle para o catch aplicável mais próximo. O código após o tratamento ainda pode ser executado quando a função captura seu próprio erro. Lance objetos Error para que tratadores posteriores recebam campos úteis.

Use await dentro do bloco protegido
async function run() {
  console.log("Working");
  try {
    await Promise.reject(new Error("Device busy"));
    console.log("Complete");
  } catch (err) {
    console.log("Failed: " + err.message);
  } finally {
    console.log("Controls enabled");
  }
}
run().catch(console.error);

A promessa rejeitada lança o erro na linha do await. O restante de try é pulado. A saída é “Working”, “Failed: Device busy” e depois “Controls enabled”. Uma operação bem-sucedida chegaria a “Complete” e ainda executaria a mesma limpeza.

Use finally para um indicador de carregamento, o estado de um botão ou outro recurso que deva ser redefinido após qualquer resultado. Ele é executado mesmo quando try ou catch tem uma instrução de retorno. Evite que a limpeza lance um erro, a menos que essa nova falha tenha a finalidade deliberada de substituir o resultado anterior. Um retorno em finally pode substituir um retorno ou erro lançado anteriormente, portanto evite usá-lo para limpeza comum.

Retornar uma promessa é diferente de esperá-la aqui
async function readSetting() {
  try {
    return await Promise.reject(new Error("Missing setting"));
  } catch {
    return "light";
  }
}
readSetting().then(value => console.log(value));

Isso registra “light”. A função espera dentro de try, portanto a rejeição chega ao catch local. Remover await retorna a promessa rejeitada imediatamente; a função adota sua rejeição, mas o catch local não trata esse resultado posterior. Quem chama precisaria capturá-lo.

Dê a cada falha um limite apropriado

Um try pode cobrir um grupo de operações await quando toda a ação deve parar se qualquer etapa falhar. Dê a uma etapa opcional um limite menor quando ela puder se recuperar de forma independente. Por exemplo, const settings = await loadSettings().catch(() => ({ theme: "light" })); usa um objeto padrão e continua.

Uma variável declarada dentro de try tem escopo de bloco. Se o código após o bloco precisar dela, declare let result; antes de try ou termine o trabalho que depende do resultado dentro de try. Não mova um valor para fora de seu escopo apenas para manter um registro final em uma posição preferida.

Quando a função atual não puder se recuperar, permita que a rejeição chegue a quem chamou. Capturá-la apenas para ocultá-la impede que quem chamou tome uma decisão útil.

Principais conclusões

  • Rejeições aguardadas lançam um erro na linha do await.
  • Catch pode recuperar, informar ou relançar um erro.
  • Finally é executado após qualquer resultado, incluindo um retorno.
  • Use return await dentro de try quando o catch local precisar tratar a rejeição da operação retornada.
  • Resultados com escopo de bloco permanecem dentro de seu bloco try.

5.3 Manter as falhas visíveis e recuperar-se de forma controlada

Uma ação precisa de um limite de captura de erros: um lugar que recebe sua falha final e escolhe uma resposta. Sem esse limite, uma promessa pode ser rejeitada enquanto ninguém trata seu resultado.

Trate o caminho final

Uma rejeição não tratada é uma rejeição que ainda não tem tratador quando o ambiente hospedeiro verifica. O tratamento pode usar catch, o argumento de rejeição de then ou try/catch em torno de await. Um navegador informa o problema e pode disparar unhandledrejection. O Node.js para por padrão em caso de rejeições não tratadas sob sua política normal, portanto deixar uma sem tratamento pode encerrar um programa.

Uma ação como main() ou um tratador de clique assíncrono deve tratar sua promessa final. Funções de nível inferior podem encaminhar falhas, mas o caminho deve terminar em algum lugar que decida o que fazer. Um ouvinte global de rejeições pode ajudar a registrar falhas não tratadas; ele não pode escolher a melhor recuperação para cada ação.

Um catch vazio suprime a falha. Registre detalhes técnicos com console.error(err) e mostre uma mensagem clara se a ação do usuário falhar. Uma mensagem como “Não foi possível salvar o rótulo. Tente novamente em instantes.” dá ao usuário uma ação a realizar sem expor um rastreamento de pilha.

Acrescente contexto sem perder a causa
async function prepareLabel() {
  try {
    return await Promise.reject(new Error("Device offline"));
  } catch (err) {
    throw new Error("Could not prepare label", { cause: err });
  }
}
prepareLabel().catch(err => {
  console.log(err.message);
  console.log(err.cause.message);
});

Isso registra “Could not prepare label” e “Device offline”. A mensagem externa identifica a ação que falhou. A propriedade cause preserva o erro de nível inferior para diagnóstico. A opção de causa de Error faz parte do ECMAScript 2022.

Tente uma nova operação novamente, com um limite
function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
async function withRetry(task, attempts) {
  for (let attempt = 1; attempt <= attempts; attempt++) {
    try {
      return await task();
    } catch (err) {
      if (attempt === attempts) throw err;
      await wait(200);
    }
  }
}

Chame essa função com uma função que inicia uma nova tentativa e um limite expresso por um número inteiro positivo. Se task falhar duas vezes e tiver sucesso na terceira chamada, um limite de quatro fará a função parar na terceira chamada e retornar seu valor. Se as quatro tentativas falharem, o quarto erro será relançado. O atraso ocorre somente entre as tentativas.

return await mantém uma rejeição dentro desse try, onde catch pode tentar novamente. Passar uma promessa já rejeitada em vez de uma função de tarefa não reiniciaria o trabalho.

Tente novamente quando uma falha puder ser temporária. Use uma alternativa de contingência quando o recurso puder funcionar com dados padrão. Pare e informe o usuário quando a ação não puder ter sucesso. Se uma função de nível inferior não puder fazer essa escolha, preserve o erro e deixe quem chamou decidir.

Principais conclusões

  • Toda ação assíncrona precisa de um tratador para a falha final.
  • Um catch vazio oculta informações úteis sobre a falha.
  • Uma causa de Error preserva a falha original enquanto acrescenta contexto.
  • Tente novamente chamando uma tarefa outra vez, com um limite finito de tentativas.
  • Escolha entre nova tentativa, alternativa de contingência, interrupção ou propagação conforme a falha.
6

Executar tarefas assíncronas em paralelo

Algumas operações precisam de resultados anteriores; outras podem começar juntas. Identifique essa dependência antes de escolher onde colocar await. Você combinará operações independentes, manterá os resultados na ordem correta e escolherá se seu programa precisa de todos os sucessos, de todos os resultados, da primeira conclusão ou do primeiro sucesso. Cada escolha altera o que uma falha significa e quanto tempo quem chama espera.

6.1 Iniciar tarefas em conjunto e aguardar com Promise.all

Operações assíncronas independentes podem esperar ao mesmo tempo. A escolha de iniciá-las juntas vem de suas necessidades de dados, não da adição da palavra async.

Inicie operações independentes antes de esperar por todas
function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
function makePart(name, ms) {
  return wait(ms).then(() => name);
}
async function assemble() {
  const parts = await Promise.all([
    makePart("cover", 1000),
    makePart("pages", 2000),
    makePart("label", 1500)
  ]);
  console.log(parts.join(", "));
}
assemble().catch(console.error);

As três chamadas começam antes de Promise.all esperar. Cerca de 2000 ms depois, o vetor contém ["cover", "pages", "label"]. Esperar por cada chamada antes de iniciar a próxima levaria cerca de 4500 ms. Os atrasos dos temporizadores são aproximados, portanto os tempos reais decorridos podem ser maiores.

Aqui, em paralelo significa esperas sobrepostas. Os tratadores de promessas ainda são executados um por vez na thread principal. Chamar três funções síncronas pesadas dentro do vetor executaria esses cálculos um após o outro; Promise.all não os moveria para threads separadas.

Os resultados mantêm suas posições de entrada

O rótulo termina antes das páginas, mas permanece em terceiro no vetor. Promise.all mantém a ordem de entrada, não a ordem de conclusão. A desestruturação pode nomear essas posições:

const [cover, pages] = await Promise.all([
  makePart("cover", 1000),
  makePart("pages", 2000)
]);

Use esse código dentro de uma função assíncrona. Para uma coleção, ids.map(id => loadItem(id)) inicia uma operação que retorna promessa por identificador e fornece a Promise.all o vetor de promessas. Não espere por cada item dentro de um laço se todos forem independentes e precisarem se sobrepor.

Uma falha encerra a espera combinada

Promise.all é rejeitado assim que uma entrada é rejeitada. Se uma parte falhar após cerca de 300 ms, um catch poderá receber esse motivo naquele momento; ele não precisa esperar pela parte bem-sucedida mais demorada.

As outras operações continuam em execução. Seus efeitos colaterais ainda podem acontecer, mas a promessa combinada não será cumprida depois com seus valores. Essa regra de falha imediata é útil quando a próxima etapa exige todos os resultados.

Respeite dependências e associe tratadores prontamente

Se uma operação precisar de um usuário produzido por outra, obtenha o usuário primeiro. As chamadas posteriores só podem ser combinadas se forem independentes umas das outras depois que esse usuário existir.

Iniciar duas promessas e esperá-las separadamente de fato sobrepõe suas esperas, mas a segunda pode ser rejeitada antes de o código chegar ao seu await. Se ela ainda não tiver tratador quando o ambiente hospedeiro verificar, uma rejeição não tratada poderá ser informada. Combinar prontamente as promessas com Promise.all associa tratamento às duas entradas e fornece a quem chama um único caminho de falha.

Valores comuns são permitidos entre as entradas e mantêm suas posições. Promise.all([]) já está cumprido com um vetor vazio. Um tratador associado a ele ainda é executado de forma assíncrona.

Principais conclusões

  • Inicie operações independentes juntas para sobrepor suas esperas.
  • Promise.all retorna resultados na ordem de entrada.
  • Uma rejeição rejeita a promessa combinada sem cancelar as outras entradas.
  • Etapas dependentes ainda precisam de seus resultados anteriores.
  • Combine prontamente promessas iniciadas para manter seus caminhos de erro conectados.

6.2 Escolher entre Promise.allSettled, Promise.race e Promise.any

O combinador de promessas adequado depende do que conta como resultado útil. Decida se você precisa de todos os sucessos, de todos os resultados, da primeira conclusão ou do primeiro sucesso.

Mantenha resultados parciais com allSettled

Promise.allSettled espera por todas as entradas e retorna registros de resultado na ordem de entrada. Um sucesso se torna { status: "fulfilled", value }; uma falha se torna { status: "rejected", reason }. Uma entrada rejeitada não rejeita a promessa combinada.

Promise.allSettled([
  Promise.resolve("Map ready"),
  Promise.reject(new Error("Chart unavailable"))
]).then(results => {
  for (const result of results) {
    if (result.status === "fulfilled") console.log(result.value);
    else console.log(result.reason.message);
  }
});

Isso registra “Map ready” e “Chart unavailable”. É adequado para painéis independentes em que um painel funcionando continua útil mesmo que outro falhe. Leia value apenas em um registro cumprido e reason em um registro rejeitado.

Deixe a primeira conclusão decidir com race

Promise.race segue a primeira entrada a ser concluída, seja ela cumprida ou rejeitada. Uma falha após 100 ms vence um sucesso após 200 ms. Isso é útil para um limite de tempo:

function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
function timeout(ms) {
  return new Promise((_, reject) => {
    setTimeout(() => reject(new Error("Time limit reached")), ms);
  });
}
Promise.race([
  wait(600).then(() => "Prepared"),
  timeout(250)
]).then(console.log).catch(err => console.log(err.message));

O limite de tempo é rejeitado primeiro, portanto a saída é “Time limit reached”. A operação de 600 ms continua em execução. Uma disputa conclui a promessa combinada de quem chama; ela não cancela as operações perdedoras.

Espere pelo primeiro sucesso com any

Promise.any ignora entradas que falharam enquanto um sucesso ainda é possível. Se uma operação falhar após 100 ms e outra for cumprida após 200 ms, ele fornecerá o valor da segunda. Uma rejeição mais rápida não vence.

Se todas as entradas forem rejeitadas, any será rejeitado com um erro agregado (AggregateError). Seu vetor errors mantém os motivos de rejeição na ordem de entrada. Isso permite que quem chama examine por que cada tentativa falhou, mesmo quando as falhas chegaram em outra ordem.

Compare as regras de decisão
  • all: todos os valores são necessários; rejeita na primeira rejeição.
  • allSettled: todos os resultados são necessários; espera por todos os registros.
  • race: a primeira conclusão decide, incluindo falha.
  • any: o primeiro cumprimento decide; rejeita somente se todos forem rejeitados.

Sem entradas, all e allSettled são cumpridos com []. any é rejeitado com AggregateError porque nenhum sucesso é possível. race permanece pendente porque nada pode concluí-lo.

Os quatro são compatíveis com navegadores atuais e com Node.js. all e race entraram no ECMAScript em 2015, allSettled em 2020 e any em 2021.

Compare um par de resultados

Suponha que ambas as operações comecem agora: a primeira entrada falha após cerca de 100 ms, enquanto a segunda é cumprida com “usable” após cerca de 300 ms. Aplicar cada combinador a esse mesmo par produz resultados diferentes:

  • All é rejeitado perto de 100 ms com o erro da primeira entrada, pois todas as entradas precisam ter sucesso.
  • AllSettled é cumprido perto de 300 ms com um registro rejeitado seguido de um registro cumprido, preservando as posições de entrada.
  • Race é rejeitado perto de 100 ms porque essa é a primeira conclusão.
  • Any é cumprido perto de 300 ms com “usable” porque a falha anterior não pode fornecer um sucesso.

All e race são rejeitados ao mesmo tempo nesse caso, mas usam regras diferentes. Se a primeira entrada rápida tivesse sucesso e a segunda falhasse depois, race manteria seu sucesso inicial enquanto all seria rejeitado pela falha posterior. Escolha conforme o resultado necessário, não conforme o tempo de um único exemplo.

Principais conclusões

  • allSettled informa cada sucesso e falha na ordem de entrada.
  • race segue a primeira conclusão, incluindo rejeição.
  • any aceita o primeiro sucesso e rejeita somente quando todas as entradas são rejeitadas.
  • Um limite de tempo com race deixa o trabalho perdedor em execução.
  • Uma entrada vazia em race permanece pendente; uma entrada vazia em any é rejeitada.
  • Escolha o combinador conforme o resultado de que quem chama realmente precisa.

Exercícios resolvidos de JavaScript assíncrono

6 Lições Interativas

1

Prever o que é executado agora e o que é executado depois

14 passos
Acompanhar um pequeno script pela pilha e pelas filas e depois usar o mesmo modelo de tempo para agendar e cancelar temporizadores.
  1. 1
    IntroduçãoSepare o início da conclusão

    Comece separando chamadas imediatas de funções de retorno registradas. Um temporizador pode esperar sem ocupar a thread principal, mas sua função de retorno ainda precisa dessa thread. Rastreie o que é executado durante o script na pilha de chamadas antes de passar ao trabalho na fila, uma tarefa ou uma microtarefa, porque a ordem delas decide o que você vê primeiro.

  2. 2
    ExemploRegistre uma função de retorno

    Leia este script antes de prever sua saída:

    console.log("request");
    setTimeout(() => console.log("ready"), 200);
    console.log("continue");
    

    O primeiro registro é executado agora. A chamada do temporizador registra uma função e retorna, portanto o script chega imediatamente ao registro final. O registro da função de retorno pertence a uma tarefa posterior; criar essa função não a chamou.

  3. 3
    Ideia-chaveA espera não ocupa a thread

    A saída começa com “request”, “continue”, seguidos de “ready” após cerca de 200 ms. O ambiente hospedeiro gerencia a espera, mas a função de retorno ainda precisa da thread principal. Um laço demorado pode atrasá-la mesmo depois que o temporizador fica pronto.

  4. 4
    DesafioO temporizador pode interromper?

    Um script registra um temporizador com atraso zero e depois realiza um cálculo síncrono demorado. O temporizador fica pronto antes de o cálculo terminar. Sua função de retorno pode ser executada durante esse cálculo?

    ---reveal---

    Não. O cálculo mantém a thread principal até que seu trabalho atual termine. Ficar pronto coloca a função de retorno na fila para depois; isso não dá a ela uma thread de execução separada nem permissão para interromper o cálculo.

  5. 5
    ExplicaçãoMantenha dois tipos de trabalho posterior

    Uma função de retorno de temporizador é executada como uma tarefa. Um tratador de promessa é executado como uma microtarefa. A pilha de chamadas registra as chamadas em execução no momento.

    Após o trabalho atual, um ponto de verificação de microtarefas esvazia a fila de microtarefas antes que outra tarefa possa começar. Todas as microtarefas adicionadas durante o esvaziamento entram nessa mesma fila. Para rastrear um script, mantenha listas separadas para a saída imediata, as microtarefas e as tarefas de temporizador.

  6. 6
    ExemploMarque as filas

    Agora acrescente um tratador de promessa:

    console.log("request");
    setTimeout(() => console.log("timer"), 0);
    Promise.resolve().then(() => console.log("job"));
    console.log("continue");
    

    A saída imediata é “request”, “continue”. O tratador de promessa está esperando como uma microtarefa; a função de retorno do temporizador é uma tarefa. Termine o script atual antes de executar qualquer uma das funções registradas.

  7. 7
    ExemploEsvazie a fila antes da próxima tarefa

    Após o término do script, a microtarefa registra “job”. Somente depois que a fila de microtarefas estiver vazia a tarefa de temporizador pronta poderá registrar “timer”.

    A saída completa é “request”, “continue”, “job”, “timer”. Uma promessa cumprida não chama seu tratador de forma síncrona, e um temporizador com atraso zero não passa à frente das microtarefas. O navegador pode renderizar em uma oportunidade de renderização após esse esvaziamento.

  8. 8
    DesafioPosicione um trabalho recém-adicionado à fila

    Qual é a ordem da saída?

    Promise.resolve().then(() => console.log("a"))
      .then(() => console.log("c"));
    queueMicrotask(() => console.log("b"));
    console.log("now");
    

    ---reveal---

    “now”, “a”, “b”, “c”. O script coloca a na fila antes de b. Somente quando o tratador de a retorna o próximo tratador da cadeia fica pronto, portanto c entra na fila depois do trabalho b, que já estava nela.

  9. 9
    ExplicaçãoEscolha um temporizador e guarde seu identificador

    Use setTimeout para uma chamada agendada e setInterval para chamadas repetidas. Ambos retornam um identificador que você pode guardar.

    const hint = setTimeout(() => console.log("Hint"), 800);
    clearTimeout(hint);
    

    Isso cancela a função de retorno antes de sua execução. Para um temporizador periódico, passe seu identificador para clearInterval quando a repetição tiver que terminar. Passe uma função para agendar; chamá-la na lista de argumentos a executa imediatamente.

  10. 10
    DicaConsidere funções de retorno atrasadas

    Os atrasos usam milissegundos e fornecem uma espera mínima. Código ocupado, abas em segundo plano e limites do navegador podem atrasar ainda mais as funções de retorno. Agende um temporizador de execução única aninhado após a execução atual; espere também pelo trabalho assíncrono dela quando as execuções não puderem se sobrepor.

  11. 11
    ExemploUse a vinculação correta do laço

    Com let, cada iteração dá à sua função de retorno um índice separado:

    for (let n = 0; n < 3; n++) {
      setTimeout(() => console.log(n), 50);
    }
    

    As funções de retorno registram “0”, “1”, “2”. Substituir let por var faz com que elas compartilhem uma variável, cujo valor é 3 após o laço. As três então registram “3”. A função de retorno lê a vinculação quando é executada depois.

  12. 12
    DesafioReinicie um temporizador para um período sem eventos

    Um tratador usa um atraso de 300 ms para adiar a ação até cessarem os eventos. Eventos de entrada ocorrem em 0 ms, 80 ms e 170 ms, e não há mais eventos. Aproximadamente quando sua única ação é executada?

    ---reveal---

    Cerca de 470 ms após o primeiro evento: 300 ms após o último evento em 170 ms. Cada evento cancela o temporizador de execução única pendente anterior e agenda outro. As ações agendadas anteriormente não são executadas, e uma thread ocupada poderia atrasar ainda mais a ação final.

  13. 13
    DesafioConecte as regras de tempo

    Um temporizador está pronto, mas cada microtarefa continua colocando outra microtarefa na fila. Por que nem a função de retorno do temporizador nem a próxima atualização da tela podem prosseguir?

    ---reveal---

    A fila de microtarefas nunca termina de ser esvaziada, portanto o laço de eventos não pode passar à próxima tarefa ou oportunidade de renderização. O término do atraso do temporizador não altera essa ordem. Agendar microtarefas sem fim pode bloquear o progresso, assim como um laço demorado.

  14. 14
    RecapitulaçãoUm rastreamento que você pode reutilizar

    Rastreie primeiro o código imediato, depois esvazie as microtarefas e então passe às tarefas prontas. Guarde identificadores de temporizadores para cancelamento. Os atrasos são aproximados; let mantém valores de iteração, enquanto o adiamento até cessarem os eventos reinicia um atraso para um período sem eventos.

2

Entregar um resultado por uma função de retorno

9 passos
Construir uma API baseada em função de retorno com um único resultado, acrescentar um caminho de erro e ver onde o trabalho dependente deve ficar.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoDê a quem chama uma próxima ação
  2. 2
    ExemploProduza o valor posterior
  3. 3
    ConexãoA API controla o momento de execução
  4. 4
    DesafioRetornar de qual função?
  5. 5
    ExemploInforme um de dois resultados
  6. 6
    DicaPare antes de ler um resultado de falha
  7. 7
    DesafioLocalize o limite de captura de erros ativo
  8. 8
    DesafioConecte resultados e falhas entre etapas
  9. 9
    RecapitulaçãoAcompanhe cada resultado até seu consumidor
3

Construir e conectar resultados de promessas

12 passos
Criar uma promessa de pausa, associar tratadores de resultado e corrigir uma cadeia dependente quebrada.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoUm objeto para um resultado posterior
  2. 2
    ExemploConstrua uma pausa reutilizável
  3. 3
    Ideia-chaveMantenha estado e resolução distintos
  4. 4
    DesafioTrate corretamente uma falha posterior
  5. 5
    ExplicaçãoAssocie o tratador correto
  6. 6
    ExemploDois registros de tratadores mantêm um valor
  7. 7
    DesafioLeia o resultado de uma limpeza
  8. 8
    ExemploConstrua o primeiro elo
  9. 9
    ExemploFaça o próximo elo esperar
  10. 10
    DesafioCorrija uma operação desconectada
  11. 11
    DesafioConecte a limpeza a uma cadeia dependente
  12. 12
    RecapitulaçãoVerifique o caminho retornado
4

Reescrever uma sequência com async e await

12 passos
Transformar uma função de retorno em uma operação que pode ser aguardada e escolher o laço correto para vários itens.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoMantenha as promessas, mude a ordem de leitura
  2. 2
    ExemploUma chamada assíncrona começa imediatamente
  3. 3
    DesafioLeia o valor retornado
  4. 4
    ExemploConecte uma função de retorno a uma promessa
  5. 5
    ExemploMantenha valores intermediários no escopo
  6. 6
    DesafioAsync, por si só, adapta o resultado?
  7. 7
    Ideia-chaveTeste o valor, não sua promessa
  8. 8
    ExemploforEach não espera por funções de retorno
  9. 9
    DicaEscolha uma forma de percorrer os itens
  10. 10
    DesafioCorrija a conclusão antecipada
  11. 11
    DesafioConecte a adaptação ao laço
  12. 12
    RecapitulaçãoExamine os dois lados de uma chamada assíncrona
5

Manter falha, recuperação e limpeza conectadas

13 passos
Acompanhar uma ação que falhou por catch, acrescentar uma alternativa de contingência local e construir uma política de novas tentativas com limite.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoDecida o que uma falha deve significar
  2. 2
    ExemploObserve um catch recuperar
  3. 3
    Ideia-chaveRegistrar também implica um retorno
  4. 4
    DesafioMantenha o resultado de falha
  5. 5
    ExemploUse await onde catch possa identificar a falha
  6. 6
    DicaRetorne após o tratamento local
  7. 7
    DesafioEscolha a alternativa de contingência local
  8. 8
    ConexãoEncerre o caminho de erro da ação
  9. 9
    ExemploMantenha o contexto e a causa original
  10. 10
    ExemploTente uma nova chamada, com um limite
  11. 11
    DesafioPare após o sucesso
  12. 12
    DesafioConecte a recuperação à limpeza
  13. 13
    RecapitulaçãoDê um destino a cada resultado
6

Escolher o que um grupo de tarefas deve fornecer

11 passos
Sobrepor esperas independentes e escolher o combinador adequado ao resultado necessário.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoComece pela questão das dependências
  2. 2
    ExemploInicie as duas operações juntas
  3. 3
    Ideia-chaveUma falha não cancela o grupo
  4. 4
    DesafioRespeite uma dependência e sobreponha o restante
  5. 5
    ExplicaçãoMantenha todos os resultados quando partes ainda forem úteis
  6. 6
    ExemploDistinga a primeira conclusão do primeiro sucesso
  7. 7
    DesafioEscolha para uma página com resultados parciais
  8. 8
    DicaUm limite de tempo encerra a espera, não a operação
  9. 9
    DesafioO que pode concluir um grupo vazio?
  10. 10
    DesafioConecte o momento de início ao tratamento de falhas
  11. 11
    RecapitulaçãoEscolha a regra de resultado

Questões de JavaScript assíncrono

40 Questões de Prática em 4 conjuntos

Conjunto 1: Passar valores por promessas e await10 Questões de Prática

Questão 1 de 10

Que valor esta promessa fornece ao seu tratador de cumprimento?

const p = new Promise((resolve, reject) => {
  resolve(14);
  reject(new Error("Changed"));
  resolve(28);
});
  1. 28
  2. 14
  3. O Error com a mensagem "Changed".
  4. undefined
Mostrar respostaOcultar resposta
Resposta: B
O primeiro resolve fixa o resultado em 14. As chamadas posteriores a reject e resolve ainda são executadas, mas não podem substituir esse resultado por uma falha ou por 28.

Questão 2 de 10

O que é registrado?

const p = Promise.resolve(7);
p.then(n => n + 4);
p.then(n => console.log(n));
  1. 11
  2. undefined
  3. Um objeto de promessa.
  4. 7
Mostrar respostaOcultar resposta
Resposta: D
Ambos os tratadores são registrados diretamente em p e recebem 7. A adição altera a nova promessa retornada por sua chamada then; ela não altera p para 11.

Questão 3 de 10

O que é registrado?

Promise.resolve(6)
  .then(n => n - 2)
  .then(n => n * 5)
  .then(console.log);
  1. 20
  2. 30
  3. 4
  4. undefined
Mostrar respostaOcultar resposta
Resposta: A
O primeiro tratador retorna 4, e o segundo multiplica esse valor retornado por 5, produzindo 20. Cada elo recebe o resultado do elo anterior. Usar 6 novamente para a multiplicação trataria elos separados da cadeia como registros independentes de tratadores na promessa original.

Questão 4 de 10

A função wait(ms), que retorna uma promessa, é cumprida após cerca de ms milissegundos. O que chega a console.log e quando?

wait(120)
  .then(() => { wait(480).then(() => "ready"); })
  .then(console.log);
  1. "ready" após cerca de 600 ms.
  2. undefined após cerca de 600 ms.
  3. undefined após cerca de 120 ms.
  4. "ready" após cerca de 480 ms.
Mostrar respostaOcultar resposta
Resposta: C
Após a primeira espera, o tratador com chaves inicia outra cadeia, mas não a retorna. Esse tratador retorna undefined, portanto a etapa externa de registro pode ser executada após cerca de 120 ms. A espera adicional de 480 ms permanece desconectada; ela não faz a cadeia externa esperar nem encaminha “ready”.

Questão 5 de 10

Que tipo de valor a chamada desta função retorna?

async function count() { return 9; }
  1. O número 9 diretamente.
  2. Uma promessa cumprida com undefined.
  3. Uma função que retornará 9.
  4. Uma promessa cumprida com 9.
Mostrar respostaOcultar resposta
Resposta: D
Funções assíncronas sempre retornam promessas, portanto retornar 9 cumpre uma promessa com 9. A chamada não fornece o número diretamente, mesmo sem um await.

Questão 6 de 10

Qual é a ordem da saída?

async function show() {
  console.log("inside");
  await 0;
  console.log("after await");
}
show();
console.log("caller");
  1. "caller", "inside", "after await"
  2. "inside", "caller", "after await"
  3. "inside", "after await", "caller"
  4. "caller", "after await", "inside"
Mostrar respostaOcultar resposta
Resposta: B
Chamar show inicia seu corpo de forma síncrona e registra “inside”. Esperar até mesmo por um valor comum adia a continuação, portanto quem chama registra sua mensagem antes de “after await”. Marcar a função como async não adia seu corpo inteiro, e esperar por 0 não mantém a continuação na chamada atual.

Questão 7 de 10

Uma API baseada em função de retorno chama done(err, value) uma vez. O que sua função envoltória baseada em promessa deve fazer quando err é um Error?
  1. Chamar resolve com err e interromper essa função de retorno.
  2. Lançar err após a função de retorno adiada retornar.
  3. Chamar reject com err e interromper essa função de retorno.
  4. Chamar resolve com value e depois reject com err.
Mostrar respostaOcultar resposta
Resposta: C
A função envoltória deve transformar o resultado de falha da função de retorno em uma promessa rejeitada. Rejeitar com err preserva esse erro para await ou catch, e interromper impede o processamento de sucesso. Resolver com um objeto Error, por sua vez, cumpriria a promessa com esse objeto como um valor comum.

Questão 8 de 10

O que é registrado?

async function permitted() { return false; }
if (permitted()) console.log("enter");
else console.log("stop");
  1. "enter"
  2. "stop"
  3. false
  4. Nada até que a promessa seja concluída.
Mostrar respostaOcultar resposta
Resposta: A
Permitted retorna um objeto de promessa, que é avaliado como verdadeiro, portanto o código registra “enter”. Aguardar a promessa dentro de uma função assíncrona testaria o valor false eventual.

Questão 9 de 10

record(id) retorna uma promessa e pode ser rejeitada. Como esta função deve processar identificadores em ordem, parar em caso de falha e permitir que quem a chama capture essa falha?

async function saveAll(ids) {
  ids.forEach(async id => { await record(id); });
}
  1. Aguardar a chamada forEach existente dentro da função.
  2. Remover await de record dentro da função de retorno assíncrona existente.
  3. Usar map com funções de retorno assíncronas, mas ignorar o vetor retornado.
  4. Substituir forEach por for...of e aguardar record dentro do laço.
Mostrar respostaOcultar resposta
Resposta: D
Um laço for...of mantém cada chamada aguardada dentro do caminho da própria promessa de saveAll. Ele inicia o próximo identificador somente após o sucesso e rejeita saveAll se uma chamada falhar. Aguardar forEach não coleta as promessas ignoradas de suas funções de retorno, portanto não garante a ordem de conclusão nem uma falha visível para quem chama.

Questão 10 de 10

Que valor o último tratador registra?

Promise.resolve(11)
  .finally(() => 40)
  .then(n => n + 2)
  .finally(() => Promise.resolve(90))
  .then(console.log);
  1. 42
  2. 13
  3. 90
  4. undefined
Mostrar respostaOcultar resposta
Resposta: B
O primeiro finally termina normalmente, preservando 11 apesar de retornar 40. A etapa then altera esse valor para 13, e o segundo finally espera por sua promessa de limpeza cumprida enquanto preserva 13. O valor de sucesso de uma promessa de limpeza não substitui o valor anterior da cadeia.
Conjunto 2: Acompanhar o código das chamadas às funções de retorno10 Questões de Prática

Salve esta aula para ver todas as respostas e praticar cada conjunto.

Questão 1 de 10

Uma página registra um temporizador e depois executa um cálculo síncrono demorado. O temporizador fica pronto durante o cálculo. Quando sua função de retorno pode ser executada?
  1. Assim que o temporizador atingir o atraso solicitado.
  2. Durante a próxima iteração do laço do cálculo.
  3. Depois que o cálculo liberar a thread principal.
  4. Em uma segunda thread criada pela chamada do temporizador.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

Qual é a ordem da saída?

console.log("open");
setTimeout(() => console.log("tick"), 0);
Promise.resolve().then(() => console.log("job"));
console.log("close");
  1. "open", "close", "job", "tick"
  2. "open", "tick", "job", "close"
  3. "open", "job", "close", "tick"
  4. "open", "close", "tick", "job"
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

Um temporizador foi agendado como const reminder = setTimeout(showHint, 900);. Qual instrução o cancela antes de sua execução?
  1. clearTimeout(showHint);
  2. setTimeout(reminder, 0);
  3. clearInterval(showHint);
  4. clearTimeout(reminder);
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

O que este laço registra após a execução de suas funções de retorno?

for (var index = 2; index < 5; index++) {
  setTimeout(() => console.log(index), 0);
}
  1. 2, 3, 4
  2. 5, 5, 5
  3. 2, 2, 2
  4. 3, 4, 5
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

Quantas chamadas de função de retorno foram concluídas quando este código chega ao registro?

let calls = 0;
[3, 6, 9].map(n => {
  calls++;
  return n + 1;
});
console.log(calls);
  1. Três chamadas foram concluídas.
  2. Nenhuma chamada foi concluída.
  3. Apenas uma chamada foi concluída.
  4. Duas chamadas foram concluídas.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

O que result contém quando o registro é executado?

function make() {
  setTimeout(() => { return "badge"; }, 0);
}
const result = make();
console.log(result);
  1. A cadeia de caracteres "badge".
  2. Uma promessa cumprida com "badge".
  3. undefined
  4. A função de retorno do temporizador.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

Uma função de retorno com erro no primeiro argumento recebe um Error e nenhum resultado em caso de falha. O que impede que o código de sucesso após esta verificação de erro leia um item ausente?

if (err) {
  console.log(err.message);
  // Add one statement here.
}
console.log(item.name);
  1. err = null;
  2. return;
  3. console.log(item);
  4. callback = null;
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

Qual é a ordem da saída?

Promise.resolve().then(() => {
  console.log("oak");
  queueMicrotask(() => console.log("elm"));
}).then(() => console.log("ash"));
queueMicrotask(() => console.log("fir"));
console.log("pine");
  1. "pine", "oak", "elm", "ash", "fir"
  2. "pine", "fir", "oak", "elm", "ash"
  3. "pine", "oak", "fir", "ash", "elm"
  4. "pine", "oak", "fir", "elm", "ash"
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

Uma operação com um único resultado usa este corpo de temporizador. O que acontece quando id é 0, e qual alteração corrige isso?

if (id <= 0) callback(new Error("Invalid"));
callback(null, { id });
  1. Chama uma vez; mova a chamada de sucesso para antes da verificação.
  2. Chama duas vezes; remova o argumento de erro da primeira chamada.
  3. Chama duas vezes; retorne após informar o erro.
  4. Chama uma vez; acrescente um retorno após a chamada de sucesso.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

Qual é a ordem da saída?

console.log("outside");
const p = new Promise(resolve => {
  console.log("executor");
  resolve("value");
});
p.then(console.log);
console.log("registered");
  1. "outside", "executor", "registered", "value"
  2. "outside", "registered", "executor", "value"
  3. "outside", "executor", "value", "registered"
  4. "executor", "outside", "registered", "value"
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Conjunto 3: Tratar falhas e combinar tarefas independentes10 Questões de Prática

Salve esta aula para ver todas as respostas e praticar cada conjunto.

Questão 1 de 10

Um tratador catch retorna "default" após receber uma rejeição. O que o tratador then seguinte recebe?
  1. O motivo original da rejeição.
  2. Um valor indefinido apesar do retorno.
  3. Uma promessa rejeitada contendo "default".
  4. O valor de cumprimento "default".
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

O que é registrado?

Promise.resolve("start")
  .then(() => { throw new Error("later"); },
        () => console.log("same call"))
  .catch(err => console.log(err.message));
  1. Apenas "same call".
  2. Apenas "later".
  3. "same call" e depois "later".
  4. "start" e depois "later".
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

Qual é a ordem da saída?

async function run() {
  try {
    await Promise.reject(new Error("busy"));
    console.log("saved");
  } catch (err) {
    console.log(err.message);
  } finally {
    console.log("reset");
  }
}
run();
  1. "saved", "reset"
  2. "reset", "busy"
  3. "busy", "reset"
  4. "busy", "saved", "reset"
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

O que quem chama registra?

async function read() {
  try {
    return Promise.reject(new Error("missing"));
  } catch {
    return "local";
  }
}
read().then(console.log).catch(err => console.log(err.message));
  1. "missing"
  2. "local"
  3. undefined
  4. Nada, porque a função permanece pendente.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

Uma função encapsula um erro como new Error("Report failed", { cause: original }). Qual propriedade mantém o Error original disponível?
  1. message
  2. name
  3. cause
  4. status
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

Uma função auxiliar de nova tentativa aguarda uma nova tarefa em try, retorna em caso de sucesso e tenta novamente em catch até seu limite de quatro chamadas. A tarefa é rejeitada nas chamadas 1 e 2 e depois é cumprida com "ok" na chamada 3. O que acontece?
  1. Ela faz quatro chamadas e é cumprida com "ok".
  2. Ela faz uma chamada e é rejeitada com o primeiro erro.
  3. Ela faz três chamadas e é rejeitada com o segundo erro.
  4. Ela faz três chamadas e é cumprida com "ok".
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

Duas funções independentes baseadas em temporizadores levam cerca de 350 ms e 900 ms. Ambas são chamadas dentro do vetor de entrada de await Promise.all(...) e ambas têm sucesso. Aproximadamente quanto tempo leva a espera combinada?
  1. Cerca de 900 ms.
  2. Cerca de 1250 ms.
  3. Cerca de 350 ms.
  4. Cerca de 550 ms.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

Tarefas são iniciadas juntas: A é cumprida após 800 ms, B é rejeitada após 250 ms, e C registra um efeito colateral e é cumprida após 600 ms. O que Promise.all([A, B, C]) faz?
  1. É rejeitado perto de 800 ms; C ainda pode registrar perto de 600 ms.
  2. É rejeitado perto de 250 ms; C ainda pode registrar perto de 600 ms.
  3. É rejeitado perto de 250 ms; C é cancelada antes de seu registro.
  4. É cumprido perto de 800 ms com A e C, mas sem o resultado de B.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

Painéis independentes devem mostrar cada um seu resultado ou seu próprio erro após todas as tentativas terminarem. Qual combinador de promessas fornece os registros necessários?
  1. Promise.allSettled
  2. Promise.all
  3. Promise.race
  4. Promise.any
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

Duas promessas começam juntas: uma é rejeitada após cerca de 150 ms e outra é cumprida com "backup" após cerca de 400 ms. Qual é a diferença entre race e any?
  1. Race é cumprido perto de 400 ms; any é rejeitado perto de 150 ms.
  2. Ambos são rejeitados perto de 150 ms com a primeira falha.
  3. Race é rejeitado perto de 150 ms; any é cumprido perto de 400 ms.
  4. Ambos são cumpridos perto de 400 ms com o valor de reserva.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Conjunto 4: Depurar fluxos assíncronos completos10 Questões de Prática

Salve esta aula para ver todas as respostas e praticar cada conjunto.

Questão 1 de 10

O que é executado primeiro quando esta instrução é avaliada?

setTimeout(showMessage(), 500);
  1. A chamada a showMessage é executada imediatamente.
  2. O temporizador espera 500 ms antes de showMessage.
  3. O temporizador coloca showMessage na fila como uma microtarefa.
  4. Uma promessa pendente precisa ser concluída primeiro.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

Uma função envoltória assíncrona inicia uma operação com função de retorno e erro no primeiro argumento, mas nunca retorna uma promessa conectada a essa função de retorno. Seu corpo chega ao fim imediatamente. O que aguardar a função envoltória fornece?
  1. O valor de sucesso eventual da função de retorno.
  2. O objeto Error eventual da função de retorno.
  3. Undefined antes de o resultado da função de retorno estar pronto.
  4. Uma espera que dura até a função de retorno ser chamada.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

inner ainda está pendente. O que acontece aqui se inner for rejeitada depois?

const outer = new Promise((resolve, reject) => {
  resolve(inner);
  reject(new Error("replacement"));
});
outer.catch(err => console.log(err.message));
  1. Outer é rejeitada com "replacement" imediatamente.
  2. Outer segue o motivo da rejeição de inner.
  3. Outer é cumprida com o objeto de promessa inner.
  4. Outer permanece pendente após inner ser rejeitada.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

Qual é a ordem da saída?

async function step() {
  console.log("a");
  await null;
  console.log("b");
  queueMicrotask(() => console.log("c"));
}
setTimeout(() => console.log("d"), 0);
step();
Promise.resolve().then(() => console.log("e"));
console.log("f");
  1. "a", "f", "e", "b", "c", "d"
  2. "a", "f", "b", "c", "e", "d"
  3. "a", "b", "f", "e", "c", "d"
  4. "a", "f", "b", "e", "c", "d"
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

O que o tratador final registra?

Promise.reject(new Error("first"))
  .catch(() => 4)
  .then(n => { throw new Error("second " + n); })
  .finally(() => 9)
  .catch(err => console.log(err.message));
  1. "first"
  2. "second 4"
  3. 9
  4. "second 9"
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

Uma chamada para obter uma conta leva cerca de 200 ms. Após ser cumprida, duas chamadas independentes baseadas na conta levam cerca de 500 ms e 800 ms. Ambas precisam dessa conta, e todos os resultados são necessários. Qual plano fornece o resultado completo mais cedo?
  1. Aguardar a conta e iniciar as duas chamadas posteriores com Promise.all; cerca de 1000 ms.
  2. Iniciar as três imediatamente em Promise.all; cerca de 800 ms.
  3. Aguardar a conta e depois cada chamada posterior em sequência; cerca de 1500 ms.
  4. Aguardar a conta e colocar as chamadas posteriores em race; cerca de 700 ms.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

Por que esta função de nova tentativa não tenta novamente uma tarefa rejeitada de forma assíncrona?

async function retry(task) {
  for (let n = 0; n < 3; n++) {
    try { return task(); }
    catch (err) { if (n === 2) throw err; }
  }
}
  1. Ela captura a rejeição, mas sai porque o laço começa em zero.
  2. Ela não pode tentar novamente porque task é passada como função em vez de promessa.
  3. Ela faz as três tentativas antes que qualquer promessa retornada possa ser concluída.
  4. Ela retorna a promessa antes que sua rejeição possa chegar ao catch local.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

Qual é o estado de Promise.race([]) quando nenhuma entrada será acrescentada?
  1. É cumprido com [].
  2. É rejeitado imediatamente.
  3. Permanece pendente.
  4. Resolve para undefined.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

Duas tentativas começam juntas. A segunda entrada é rejeitada primeiro; a primeira entrada é rejeitada depois. O que Promise.any([first, second]) fornece ao catch ao final?
  1. O Error da segunda entrada assim que ela é rejeitada.
  2. Um AggregateError com os motivos na ordem primeira, depois segunda.
  3. Um AggregateError com os motivos na ordem segunda, depois primeira.
  4. Apenas o Error da primeira entrada após a rejeição posterior.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

O que quem chama registra?

async function value() {
  try {
    return await Promise.reject(new Error("lost"));
  } catch {
    return "fallback";
  } finally {
    console.log("cleanup");
  }
}
value().then(console.log);
console.log("caller");
  1. "cleanup", "caller", "fallback"
  2. "caller", "fallback", "cleanup"
  3. "caller", "cleanup", "lost"
  4. "caller", "cleanup", "fallback"
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Travou em uma questão sua? Ajuda com Tarefas

Termos-Chave

72 Termos-Chave, cada um com sua definição

A

Adiamento até cessarem os eventos
O adiamento até cessarem os eventos reinicia um temporizador a cada evento para que uma ação espere por um período sem eventos.
Digitar vários caracteres rapidamente substitui o temporizador pendente a cada vez, deixando uma atualização após o fim da digitação.
Adoção de promessa
A adoção de promessa faz uma promessa seguir o valor de cumprimento ou motivo de rejeição eventual de outra promessa em vez de tratá-la como um objeto comum.
Retornar uma promessa de then faz o próximo tratador esperar e receber seu valor, sem uma promessa aninhada adicional.
Alternativa de contingência
Uma alternativa de contingência é um valor ou comportamento alternativo usado após uma falha quando a ação maior ainda pode realizar trabalho útil sem seu resultado original.
Uma configuração opcional de tema ausente é substituída por um tema claro enquanto o restante da página continua carregando.
Ambiente hospedeiro
O ambiente hospedeiro é o ambiente de execução do navegador ou do Node.js que fornece recursos como temporizadores e gerencia seu agendamento.
O navegador acompanha o atraso de um temporizador e depois disponibiliza sua função de retorno para execução posterior pelo JavaScript.
Ausência de return
A ausência de return deixa o resultado de um tratador como undefined e pode desconectar o trabalho que ele deveria retornar.
Um tratador com chaves inicia uma pausa, mas esquece de retorná-la, portanto o próximo tratador é executado antes de a pausa terminar.
await no nível superior
await no nível superior é o uso de await diretamente no corpo de um módulo JavaScript, onde a linguagem o permite fora de uma função assíncrona.
Um módulo pode aguardar sua inicialização diretamente, enquanto uma função comum ainda precisa de sua própria palavra-chave async.

B

Bloco finally
Um bloco finally é executado quando o controle sai de sua estrutura try/catch, fornecendo limpeza após sucesso, falha ou uma instrução de retorno.
Um botão é ativado em finally para que tentativas bem-sucedidas e malsucedidas deixem a interface pronta para outra ação.
Bloco try
Um bloco try protege instruções cujos erros lançados podem transferir o controle para seu catch associado.
Aguardar uma operação de preparação dentro de try permite que a função envolvente se recupere quando essa operação for rejeitada depois.
Bloqueio
O bloqueio ocupa uma thread com trabalho, impedindo que outro código agendado seja executado nela.
Um laço que mantém a thread ocupada pode atrasar um temporizador com atraso zero porque a função de retorno ainda precisa da thread principal ocupada.

C

Cadeia de promessas
Uma cadeia de promessas conecta tratadores por meio de promessas retornadas, passando cada valor retornado ou resultado assíncrono adotado ao próximo elo.
Um tratador retorna uma promessa de preparação para que o próximo receba o item concluído após o fim da preparação.
Cadeia sem aninhamento
Uma cadeia sem aninhamento expressa etapas dependentes de promessas como tratadores sucessivos no nível externo, mantendo conectados seu tempo de execução e a propagação de erros.
Retornar cada nova operação permite que um catch no final receba falhas das etapas conectadas anteriores.
Causa do erro
Uma causa do erro é uma falha original mantida na propriedade cause de um novo Error.
Um erro externo que diz “Report failed” ainda guarda, em sua causa, o erro do dispositivo que impediu a preparação do relatório.
Código síncrono
O código síncrono termina cada instrução antes de passar para a próxima.
Um laço demorado atrasa o próximo registro porque cada iteração é executada antes que o controle possa sair do laço.
Comportamento de falha imediata
O comportamento de falha imediata rejeita uma espera combinada assim que uma entrada necessária falha, sem esperar que as entradas restantes terminem.
Uma preparação agrupada falha quando uma parte falha após 200 ms, embora outra parte ainda esteja trabalhando para sua conclusão posterior.
Concluída
Uma promessa concluída foi cumprida ou rejeitada, fixando seu estado e seu resultado.
Chamar resolve novamente após uma promessa ter sido cumprida não substitui o valor recebido por seus tratadores.
Concorrência
A concorrência permite que operações independentes permaneçam em andamento durante esperas sobrepostas, enquanto seus tratadores JavaScript ainda são executados um por vez.
Dois temporizadores começam juntos, portanto quem chama espera aproximadamente o tempo do temporizador mais lento em vez de somar os dois atrasos.
Continuação
Uma continuação é o código de uma função assíncrona que retoma a execução após um await.
Mesmo aguardar um número comum adia as linhas restantes em vez de executá-las antes de quem chamou continuar.
Conversão para promessas
A conversão para promessas adapta uma API baseada em função de retorno para uma API que retorna promessa, conectando o sucesso e a falha da função de retorno à conclusão da promessa.
Uma função envoltória de uma API de função de retorno de rótulo resolve com o rótulo ou rejeita com o erro para que quem chama de forma assíncrona possa aguardar a operação concluída.
Cumprida
Cumprida é o estado concluído de uma promessa que contém um valor de sucesso.
Uma verificação de quantidade bem-sucedida é cumprida com a quantidade restante, e seu tratador de sucesso recebe esse número.

E

Erro agregado
Um erro agregado agrupa vários erros; Promise.any o usa com um vetor errors na ordem de entrada quando todas as tentativas falham.
Quando duas tentativas falham em ordem inversa, o vetor errors ainda lista seus motivos na ordem de entrada original.
Erro suprimido
Um erro suprimido é uma falha capturada e ocultada sem comunicação útil ou propagação, tornando mais difícil diagnosticar a ação que falhou.
Um catch vazio em torno de uma operação de salvamento deixa a página em silêncio mesmo que o salvamento não tenha sido concluído com sucesso.
Evento de erro
Um evento de erro informa uma falha por um tratador de evento, permitindo que uma API do navegador use um caminho de falha separado de seu evento de sucesso.
Uma imagem tem seu tratador de falha definido antes de sua origem começar a carregar, junto com seu tratador de sucesso.
Execução até a conclusão
Execução até a conclusão significa que, quando uma tarefa ou microtarefa começa, funções de retorno colocadas na fila de forma assíncrona não podem interromper seu código em execução.
Um temporizador que fica pronto no meio de um laço não insere sua função de retorno entre as instruções do laço.

F

Função assíncrona
Uma função assíncrona sempre retorna uma promessa para seu resultado retornado ou erro lançado.
Chamar uma função assíncrona que retorna um número fornece a quem chamou uma promessa, mesmo sem await em seu corpo.
Função de retorno
Uma função de retorno é uma função passada a outra função para que esta a invoque.
Passar um tratador nomeado fornece uma ação; acrescentar parênteses o chamaria durante a avaliação do argumento.
Função de retorno assíncrona
Uma função de retorno assíncrona é invocada depois por meio de agendamento, após a operação que a registrou ter retornado a quem a chamou.
Uma função de preparação de rótulo retorna primeiro; sua função de retorno de resultado recebe o rótulo concluído depois.
Função de retorno com erro no primeiro argumento
Uma função de retorno com erro no primeiro argumento recebe um argumento de erro antes do resultado, usando null para sucesso e um objeto Error para falha.
Quem chama verifica o erro antes de ler o item, para que uma operação que falhou não inicie o processamento de sucesso.
Função de retorno de finally
Uma função de retorno de finally é executada após qualquer resultado de uma promessa, sem receber argumento e normalmente preservando o valor ou motivo anterior.
A função de retorno oculta um indicador de carregamento após sucesso ou falha; um erro lançado durante essa limpeza se tornaria uma nova falha.
Função de retorno síncrona
Uma função de retorno síncrona é invocada antes de a função que a recebe retornar, portanto seu trabalho acontece durante a chamada atual dessa função.
A função de retorno passada para map dobra cada elemento antes de o vetor de resultados completo ser retornado.
Função envoltória baseada em promessa
Uma função envoltória baseada em promessa expõe o resultado de outra API por meio de uma promessa retornada, permitindo que quem chama consuma o resultado com tratadores ou await.
Encapsular um temporizador cria uma pausa reutilizável cuja conclusão está conectada à promessa retornada a quem chamou.
Função executora
Uma função executora é a função que o construtor Promise chama imediatamente com resolve e reject.
A função executora registra um temporizador imediatamente; o tratador de sucesso ainda é executado depois pela fila de microtarefas.

I

Identificador de temporizador
Um identificador de temporizador identifica um temporizador agendado para que o programa possa cancelá-lo.
Guardar o identificador retornado permite que o próximo evento de entrada cancele a atualização de sugestões pendente anterior.
Inanição causada por microtarefas
A inanição causada por microtarefas atrasa tarefas e renderização porque a fila de microtarefas nunca termina de ser esvaziada.
Uma microtarefa que sempre coloca outra cópia de si na fila pode impedir que um tratador de clique tenha sua vez.
Inferno de funções de retorno
O inferno de funções de retorno é um código de funções de retorno profundamente aninhado em que cada etapa dependente acrescenta outro nível e, muitas vezes, outro ramo de tratamento de erros.
Ler um perfil, depois projetos e depois tarefas cria várias funções de retorno aninhadas difíceis de examinar.

L

Laço de eventos
O laço de eventos coordena tarefas, pontos de verificação de microtarefas e oportunidades de renderização para que o trabalho pronto seja executado de forma ordenada.
Rastrear primeiro um script e depois suas microtarefas explica por que a mensagem da promessa de um script aparece antes da mensagem do temporizador dele.
Lançamento de exceção
Um lançamento de exceção interrompe a execução normal naquele ponto e transfere um erro para um catch aplicável ou o propaga para fora se nenhum se aplicar.
Uma promessa rejeitada que é aguardada se comporta como um lançamento de exceção na linha do await, pulando as instruções restantes em try.
Limite de captura de erros
Um limite de captura de erros é o conjunto de trabalhos conectados cujas falhas podem chegar a um tratador catch específico, determinado pela posição do tratador e pelas chamadas aguardadas.
Um catch no meio trata etapas anteriores, mas não pode tratar um novo erro lançado por um tratador posterior.

M

Método Promise.all
O método Promise.all combina promessas de entrada em uma promessa para todos os valores de sucesso na ordem de entrada, sendo rejeitado na primeira rejeição de entrada.
A primeira entrada, mais lenta, permanece em primeiro no vetor de resultados mesmo quando a segunda entrada termina antes.
Método Promise.allSettled
O método Promise.allSettled espera por todas as entradas e é cumprido com registros na ordem de entrada que descrevem cada valor de cumprimento ou motivo de rejeição.
Uma página mantém um painel que funcionou e exibe um erro para um painel que falhou após examinar os dois registros de resultado.
Método Promise.any
O método Promise.any é cumprido com o primeiro valor de entrada bem-sucedido e é rejeitado com AggregateError somente quando todas as entradas foram rejeitadas.
Uma tentativa inicial que falhou é ignorada enquanto outra tentativa ainda tem chance de fornecer um resultado utilizável.
Método Promise.race
O método Promise.race segue a primeira entrada a ser concluída, tomando seu valor de sucesso ou motivo de rejeição como resultado combinado.
Um limite de tempo que rejeita vence uma operação bem-sucedida mais lenta, enquanto a operação lenta continua em execução.
Microtarefa
Uma microtarefa é um trabalho na fila, como um tratador de promessa, executado em um ponto de verificação de microtarefas antes de o laço de eventos iniciar outra tarefa.
O tratador de uma promessa cumprida é executado após o script atual, mas antes de uma tarefa pronta de temporizador com atraso zero.

N

Nova tentativa
Uma nova tentativa inicia uma nova execução após uma falha, geralmente com um atraso e um limite finito de tentativas quando a falha pode ser temporária.
Uma tarefa tem sucesso em sua terceira chamada, portanto uma função auxiliar com quatro tentativas disponíveis retorna sem fazer uma quarta chamada.

O

Objeto de erro
Um objeto de erro representa uma falha com uma mensagem e um nome e normalmente inclui informações de pilha úteis para localizar o problema.
Ler a mensagem de um Error capturado criado com a mensagem “Missing label” fornece esse texto sem exigir que a interface do usuário mostre seu rastreamento de pilha.
Omissão de await
A omissão de await deixa uma promessa em um lugar onde o código espera seu valor eventual, causando condições ou operações numéricas incorretas.
Multiplicar a promessa retornada por uma função de quantidade produz NaN em vez de multiplicar seu número futuro.
Operação assíncrona
Uma operação assíncrona começa agora e entrega seu resultado depois, enquanto outro código pode continuar.
Um temporizador é registrado agora, enquanto sua função de retorno registra uma mensagem depois que o ambiente gerencia o atraso.
Operação dependente
Uma operação dependente precisa do resultado de uma operação anterior e deve começar somente quando essa entrada necessária estiver disponível.
Uma função de retorno recebe um usuário e depois inicia a próxima operação com esse usuário como argumento necessário.
Operações independentes
Operações independentes podem começar sem os resultados umas das outras, permitindo que seus tempos de espera se sobreponham quando o programa as inicia juntas.
A preparação de uma capa e de um rótulo pode se sobrepor quando ambos já têm todos os dados de entrada necessários.
Operador await
O operador await pausa sua função assíncrona até que um resultado esteja disponível, fornecendo um valor de cumprimento ou lançando o motivo da rejeição nessa linha.
Aguardar uma pausa deixa quem chamou livre para registrar uma mensagem antes de a função pausada continuar sua próxima instrução.
Ordem de entrada
A ordem de entrada é a disposição dos valores ou promessas fornecidos a um combinador.
A desestruturação do resultado de Promise.all nomeia os valores conforme as posições do vetor original.

P

Pendente
Pendente é o estado da promessa antes do cumprimento ou da rejeição.
Uma pausa baseada em temporizador permanece pendente enquanto o ambiente espera para invocar a função que a resolve.
Pilha de chamadas
A pilha de chamadas registra chamadas de função ativas, com a chamada atual no topo.
Se um tratador chama uma função auxiliar, ela é executada no topo da pilha até retornar a esse tratador.
Processamento sequencial
O processamento sequencial inicia cada etapa assíncrona após a conclusão da anterior, preservando dependências ou uma ordem necessária de operações.
Um laço for...of aguarda uma operação de registro antes de iniciar a próxima e registra a conclusão após o último sucesso.
Promessa
Uma promessa é um objeto que representa um único resultado eventual, capaz de fornecer um valor ou um motivo de falha aos tratadores conectados.
Uma função retorna uma promessa imediatamente, e depois um temporizador fornece o valor que quem chamou está aguardando.
Promessa de função de retorno ignorada
Uma promessa de função de retorno ignorada é uma promessa retornada por uma função de retorno que a API que a chama não usa para acompanhar a conclusão ou a falha.
O método forEach ignora a promessa de uma função de retorno assíncrona, portanto a função ao redor pode terminar antes desse trabalho.
Propagação de rejeições
A propagação de rejeições conduz uma falha por elos conectados de promessas, pulando tratadores de cumprimento até que um tratador de rejeição lide com ela.
Um erro lançado na segunda etapa chega ao catch final sem executar o tratador de sucesso da terceira etapa.

R

Recuperação
A recuperação transforma um caminho de promessa que falhou em um caminho bem-sucedido ao retornar um valor utilizável ou uma promessa cumprida de um tratador de rejeição.
Retornar um rótulo padrão em catch dá ao próximo tratador de sucesso um valor que ele pode usar para continuar.
Registro de tratador em uma promessa
O registro de tratador em uma promessa associa um tratador a uma promessa específica.
Dois tratadores em uma promessa cumprida recebem cada um seu número original, mesmo quando um retorna um número alterado.
Rejeição não tratada
Uma rejeição não tratada é uma rejeição de promessa que ainda não tem tratador de rejeição quando o ambiente hospedeiro verifica se há rejeições não tratadas.
Uma ação em segundo plano iniciada sem catch pode ser rejeitada depois que quem chamou já continuou, deixando sua falha sem tratamento.
Rejeitada
Rejeitada é o estado concluído de uma promessa que contém um motivo de falha.
Uma verificação de estoque que falha é rejeitada com um Error cuja mensagem explica por que a quantidade solicitada não está disponível.
Relançamento de erro
O relançamento de erro é o ato de lançar novamente um erro capturado para que um tratador posterior possa recebê-lo.
Um catch de nível inferior registra um erro e o relança, deixando a ação externa responsável pela mensagem ao usuário.
Resolvida
Uma promessa resolvida tem seu resultado definido, possivelmente pela adoção de outra promessa que ainda está pendente e pode ser rejeitada depois.
Resolver uma promessa de função envoltória com uma promessa pendente faz com que ela siga o sucesso ou a falha posterior dessa promessa.
Resultado intermediário
Um resultado intermediário é um valor produzido durante uma sequência e usado por uma etapa posterior.
Uma função assíncrona armazena o usuário e depois os pedidos, mantendo ambos os valores locais disponíveis para a mensagem final.
Retorno antecipado
Um retorno antecipado encerra uma função antes que suas instruções restantes sejam executadas.
Retornar após registrar um erro de função de retorno impede que a linha seguinte leia o nome de um resultado ausente.

T

Tarefa
Uma tarefa é uma unidade de trabalho agendada, como um script, um tratador de evento ou uma função de retorno de temporizador.
O navegador termina o script atual antes de iniciar uma função de retorno de temporizador que ficou pronta.
Temporizador de execução única
Um temporizador de execução única agenda uma função de retorno para ser executada uma vez após um atraso, sendo a execução efetiva também dependente do agendamento.
Uma dica agendada para 600 ms pode aparecer mais tarde se a thread principal ainda estiver fazendo outro trabalho nesse momento.
Temporizador de execução única aninhado
Um temporizador de execução única aninhado agenda outro temporizador de execução única a partir de uma função de retorno de temporizador, permitindo que o próximo atraso comece após o trabalho da execução atual.
Uma verificação de progresso agenda seu próximo temporizador de execução única somente após terminar sua verificação síncrona atual.
Temporizador periódico
Um temporizador periódico agenda repetidamente uma função de retorno de temporizador até ser cancelado.
Um contador cancela seu temporizador periódico após o quinto disparo para que a página pare de produzir novas mensagens de disparo.
Thread principal
A thread principal executa o JavaScript comum da página e o trabalho relacionado à tela.
Um cálculo demorado na thread principal impede a execução de um tratador de clique pronto até que o cálculo termine.
Tratador de cumprimento
Um tratador de cumprimento é uma função registrada com then que recebe o valor de sucesso de uma promessa e determina o resultado de uma nova promessa.
Um tratador multiplica a quantidade recebida e retorna a nova quantidade para o próximo elo da cadeia.
Tratador de rejeição
Um tratador de rejeição recebe o motivo de falha de uma promessa e pode recuperar com um valor ou preservar a falha lançando um erro.
Um tratador catch fornece configurações padrão quando uma operação de configurações que falhou não deve interromper a ação maior.

V

Valor avaliado como verdadeiro
Um valor avaliado como verdadeiro é tratado como verdadeiro em uma condição; uma promessa é avaliada como verdadeira por ser um objeto, independentemente de seu resultado eventual.
Uma promessa cumprida com false ainda faz o código entrar em um ramo if quando ele testa diretamente o objeto de promessa.

Sobre esta aula

Por StudeliRelatar um erro

Crie seus próprios Materiais de Estudo

Transforme suas anotações, slides e documentos em Guias de Estudo, Questões de Prática, Flashcards e mais 6 ferramentas.

Começar