- Aprender
- Computação e TI
- Programação
- Programação em JavaScript
- JavaScript assíncrono
JavaScript assíncrono
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
- 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íncronoExplicar 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 eventosRastrear 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 temporizadoresExecutar 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.
- Distinguir código síncrono de código assíncrono
- 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íncronasEscrever 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 excessivoUsar 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.
- Passar funções de retorno para operações assíncronas
- 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 promessaDescrever 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 finallyRegistrar 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ênciaRetornar valores e promessas dos tratadores para construir cadeias sem aninhamento de etapas dependentes e evitar erros de ausência de return e de aninhamento.
- Explicar os estados de uma promessa e criar uma promessa
- 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 awaitDeclarar 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 awaitTransformar 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 awaitReconhecer 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.
- Escrever funções assíncronas e usar await
- 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 promessasAcompanhar 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 awaitUsar 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 controladaEvitar 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.
- Tratar rejeições em cadeias de promessas
- 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.allExecutar 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.anyUsar 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.
- Iniciar tarefas em conjunto e aguardar com Promise.all
Guia de estudo de JavaScript assíncrono
1Explicar 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");
- O script registra “begin”.
- Ele registra uma função de retorno de temporizador; não a chama.
- Ele coloca o tratador da promessa cumprida na fila como uma microtarefa.
- Ele registra “finish” e termina o trabalho atual.
- A microtarefa registra “promise”.
- 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.
2Escrever 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.
3Criar 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.
4Escrever 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.
5Tratar 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.
6Executar 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
1Prever o que é executado agora e o que é executado depois
14 passos
- 1Introduçã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.
- 2ExemploRegistre 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.
- 3Ideia-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.
- 4DesafioO 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.
- 5Explicaçã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.
- 6ExemploMarque 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.
- 7ExemploEsvazie 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.
- 8DesafioPosicione 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.
- 9ExplicaçãoEscolha um temporizador e guarde seu identificador
Use
setTimeoutpara uma chamada agendada esetIntervalpara 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
clearIntervalquando a repetição tiver que terminar. Passe uma função para agendar; chamá-la na lista de argumentos a executa imediatamente. - 10DicaConsidere 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.
- 11ExemploUse 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.
- 12DesafioReinicie 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.
- 13DesafioConecte 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.
- 14Recapitulaçã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.
2Entregar um resultado por uma função de retorno
9 passos
Salve esta aula para seguir cada Lição Interativa passo a passo.
- 1IntroduçãoDê a quem chama uma próxima ação
- 2ExemploProduza o valor posterior
- 3ConexãoA API controla o momento de execução
- 4DesafioRetornar de qual função?
- 5ExemploInforme um de dois resultados
- 6DicaPare antes de ler um resultado de falha
- 7DesafioLocalize o limite de captura de erros ativo
- 8DesafioConecte resultados e falhas entre etapas
- 9RecapitulaçãoAcompanhe cada resultado até seu consumidor
3Construir e conectar resultados de promessas
12 passos
Salve esta aula para seguir cada Lição Interativa passo a passo.
- 1IntroduçãoUm objeto para um resultado posterior
- 2ExemploConstrua uma pausa reutilizável
- 3Ideia-chaveMantenha estado e resolução distintos
- 4DesafioTrate corretamente uma falha posterior
- 5ExplicaçãoAssocie o tratador correto
- 6ExemploDois registros de tratadores mantêm um valor
- 7DesafioLeia o resultado de uma limpeza
- 8ExemploConstrua o primeiro elo
- 9ExemploFaça o próximo elo esperar
- 10DesafioCorrija uma operação desconectada
- 11DesafioConecte a limpeza a uma cadeia dependente
- 12RecapitulaçãoVerifique o caminho retornado
4Reescrever uma sequência com async e await
12 passos
Salve esta aula para seguir cada Lição Interativa passo a passo.
- 1IntroduçãoMantenha as promessas, mude a ordem de leitura
- 2ExemploUma chamada assíncrona começa imediatamente
- 3DesafioLeia o valor retornado
- 4ExemploConecte uma função de retorno a uma promessa
- 5ExemploMantenha valores intermediários no escopo
- 6DesafioAsync, por si só, adapta o resultado?
- 7Ideia-chaveTeste o valor, não sua promessa
- 8ExemploforEach não espera por funções de retorno
- 9DicaEscolha uma forma de percorrer os itens
- 10DesafioCorrija a conclusão antecipada
- 11DesafioConecte a adaptação ao laço
- 12RecapitulaçãoExamine os dois lados de uma chamada assíncrona
5Manter falha, recuperação e limpeza conectadas
13 passos
Salve esta aula para seguir cada Lição Interativa passo a passo.
- 1IntroduçãoDecida o que uma falha deve significar
- 2ExemploObserve um catch recuperar
- 3Ideia-chaveRegistrar também implica um retorno
- 4DesafioMantenha o resultado de falha
- 5ExemploUse await onde catch possa identificar a falha
- 6DicaRetorne após o tratamento local
- 7DesafioEscolha a alternativa de contingência local
- 8ConexãoEncerre o caminho de erro da ação
- 9ExemploMantenha o contexto e a causa original
- 10ExemploTente uma nova chamada, com um limite
- 11DesafioPare após o sucesso
- 12DesafioConecte a recuperação à limpeza
- 13RecapitulaçãoDê um destino a cada resultado
6Escolher o que um grupo de tarefas deve fornecer
11 passos
Salve esta aula para seguir cada Lição Interativa passo a passo.
- 1IntroduçãoComece pela questão das dependências
- 2ExemploInicie as duas operações juntas
- 3Ideia-chaveUma falha não cancela o grupo
- 4DesafioRespeite uma dependência e sobreponha o restante
- 5ExplicaçãoMantenha todos os resultados quando partes ainda forem úteis
- 6ExemploDistinga a primeira conclusão do primeiro sucesso
- 7DesafioEscolha para uma página com resultados parciais
- 8DicaUm limite de tempo encerra a espera, não a operação
- 9DesafioO que pode concluir um grupo vazio?
- 10DesafioConecte o momento de início ao tratamento de falhas
- 11RecapitulaçãoEscolha a regra de resultado
Questões de JavaScript assíncrono
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);
});
Mostrar respostaOcultar resposta
Questão 2 de 10
O que é registrado?
const p = Promise.resolve(7);
p.then(n => n + 4);
p.then(n => console.log(n));
Mostrar respostaOcultar resposta
Questão 3 de 10
O que é registrado?
Promise.resolve(6)
.then(n => n - 2)
.then(n => n * 5)
.then(console.log);
Mostrar respostaOcultar resposta
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);
Mostrar respostaOcultar resposta
Questão 5 de 10
Que tipo de valor a chamada desta função retorna?
async function count() { return 9; }
Mostrar respostaOcultar resposta
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");
Mostrar respostaOcultar resposta
Questão 7 de 10
done(err, value) uma vez. O que sua função envoltória baseada em promessa deve fazer quando err é um Error?Mostrar respostaOcultar resposta
Questão 8 de 10
O que é registrado?
async function permitted() { return false; }
if (permitted()) console.log("enter");
else console.log("stop");
Mostrar respostaOcultar resposta
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); });
}
Mostrar respostaOcultar resposta
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);
Mostrar respostaOcultar resposta
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
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");
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 3 de 10
const reminder = setTimeout(showHint, 900);. Qual instrução o cancela antes de sua execução?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);
}
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);
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);
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);
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");
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 });
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");
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
"default" após receber uma rejeição. O que o tratador then seguinte recebe?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));
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();
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));
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 5 de 10
new Error("Report failed", { cause: original }). Qual propriedade mantém o Error original disponível?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 6 de 10
"ok" na chamada 3. O que acontece?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 7 de 10
await Promise.all(...) e ambas têm sucesso. Aproximadamente quanto tempo leva a espera combinada?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 8 de 10
Promise.all([A, B, C]) faz?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 9 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 10 de 10
"backup" após cerca de 400 ms. Qual é a diferença entre race e any?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);
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 2 de 10
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));
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");
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));
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 6 de 10
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; }
}
}
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 8 de 10
Promise.race([]) quando nenhuma entrada será acrescentada?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 9 de 10
Promise.any([first, second]) fornece ao catch ao final?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");
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Travou em uma questão sua? Ajuda com Tarefas
Termos-Chave
A
Adiamento até cessarem os eventos
Adoção de promessa
Alternativa de contingência
Ambiente hospedeiro
Ausência de return
await no nível superior
B
Bloco finally
Bloco try
Bloqueio
C
Cadeia de promessas
Cadeia sem aninhamento
Causa do erro
Código síncrono
Comportamento de falha imediata
Concluída
Concorrência
Continuação
Conversão para promessas
Cumprida
E
Erro agregado
Erro suprimido
Evento de erro
Execução até a conclusão
F
Função assíncrona
Função de retorno
Função de retorno assíncrona
Função de retorno com erro no primeiro argumento
Função de retorno de finally
Função de retorno síncrona
map dobra cada elemento antes de o vetor de resultados completo ser retornado.Função envoltória baseada em promessa
Função executora
I
Identificador de temporizador
Inanição causada por microtarefas
Inferno de funções de retorno
L
Laço de eventos
Lançamento de exceção
Limite de captura de erros
M
Método Promise.all
Método Promise.allSettled
Método Promise.any
Método Promise.race
Microtarefa
N
Nova tentativa
O
Objeto de erro
Omissão de await
Operação assíncrona
Operação dependente
Operações independentes
Operador await
Ordem de entrada
P
Pendente
Pilha de chamadas
Processamento sequencial
Promessa
Promessa de função de retorno ignorada
Propagação de rejeições
R
Recuperação
Registro de tratador em uma promessa
Rejeição não tratada
Rejeitada
Relançamento de erro
Resolvida
Resultado intermediário
Retorno antecipado
T
Tarefa
Temporizador de execução única
Temporizador de execução única aninhado
Temporizador periódico
Thread principal
Tratador de cumprimento
Tratador de rejeição
V
Valor avaliado como verdadeiro
Sobre esta aula
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.