Se você se tornou engenheiro frontend após 2015, é provável que tenha usado ou pelo menos ouvido falar do 'Promise'. Como já mencionei no meu blog anterior, o 'Promise' pode nos ajudar a especificar relações sequenciais entre operações em programação assíncrona de forma legível e sustentável. O objeto 'Promise' em JavaScript possui métodos, como 'then' e 'catch', que podem nos ajudar a organizar a execução sequencial de operações em um padrão chamado Promise Chaining. Por mais limpo e organizado que o Promise Chaining seja, algumas pessoas — especialmente aquelas mais acostumadas à programação síncrona — acham isso pouco intuitivo e confuso. Felizmente para eles, o ECMAScript 2017 introduziu um novo recurso que nos permite escrever código assíncrono como se fosse síncrono. Esse novo recurso consiste em duas palavras-chave: 'assíncrono' e 'await'. Neste artigo, abordarei os fundamentos de 'async/await' e como fazer a transição do Promise Chaining para o uso de 'async/await'.
Aguarde: Espera por uma promessa a ser cumprida
Vou começar com a palavra-chave 'await'. Usamos 'await' ao invocar uma função que retorna um objeto 'Promise'. Ela bloqueia todas as operações subsequentes até a conclusão (ou falha) desse objeto 'Promise'. Também pode ajudar a atribuir o valor de cumprimento do objeto 'Promise' a uma constante ou variável. A seguir está um exemplo para ilustrar seu uso principal.
função getNumberOneInOneSecond() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(1)
}, 1000)
})
}
Vamos supor que temos uma função chamada 'getNumberOneInOneSecond' que retorna um objeto 'Promise'. Ao construir esse objeto 'Promise', passamos para o construtor uma função anônima cujo argumento é uma função resolve. Na função anônima, usamos 'setTimeOut' para invocar a função 'resolve' com o número um como argumento após um segundo.
Se inicializarmos uma constante com o valor retornado de 'getNumberOneInOneSecond', a constante será uma instância de 'Promise'.
função getNumberOneInOneSecond() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(1)
}, 1000)
})
}
const numberOnePromise = getNumberOneInOneSecond()
console.log(instância de Promessa número Um)
No entanto, intuitivamente, esperamos que a constante seja a número um. É aí que a palavra-chave 'await' é útil. Podemos usar a palavra-chave 'await' para atribuir diretamente o valor de cumprimento do objeto 'Promise' à constante 'numberOne'. Como mencionado acima, 'await' bloqueia todas as operações subsequentes quando o objeto 'Promise' ainda está pendente. Portanto, o console imprime a constante 'numberOne' somente após o objeto 'Promise' ser cumprido.
função getNumberOneInOneSecond() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(1)
}, 1000);
})
}
Função assíncrona main() {
console.log('Recebendo o número um...') // Executado imediatamente
const numberOne = await getNumberOneInOneSecond()
console.log(número um)
}
main()
Assíncrono: um santuário para a espera e uma fábrica produzindo promessas
A palavra-chave 'await' nos ajuda a escrever código meio síncrono em programação assíncrona, mas não podemos usá-la onde quisermos. Você pode estar confuso sobre por que decidi envolver 'await getOneNumberInOneSecond()' na função 'main' e marcar com 'async' no exemplo de código acima. Bem, certamente não fiz isso só por diversão. Tive que fazer porque só podemos usar a palavra-chave 'await' em funções marcadas como 'async'.
Além disso, 'assync' pode transformar uma função regular em uma que retorna um objeto 'Promise'. Vamos supor que tenhamos uma função chamada 'multiplyByTwo'. Ela recebe um argumento. Primeiro, verifica se o argumento é um número. Se o argumento for um número, então 'multiplyByTwo' multiplica por dois e retorna o produto. Caso contrário, 'multiplyByTwo' gera um erro.
função multiplyByTwo(input) {
se (tipo de entrada === "número") {
Saída const = entrada * 2
Retorno de saída
} else {
const error = novo erro ("Entrada Inválida")
erro de arremesso
}
}
Se adicionarmos a palavra-chave 'async' antes da função, ela se torna uma função que retorna um objeto 'Promise'. O objeto 'Promise' retornado é resolvido com o valor de retorno da função original e rejeitado com o erro lançado pela função original.
função async multiplyByTwo(input) {
se (tipo de entrada === "número") {
Saída const = entrada * 2
Retorno de saída
} else {
const error = novo erro ("Entrada Inválida")
erro de arremesso
}
}
É equivalente à função seguinte, que usei como exemplo no blog anterior.
função multiplyByTwo(input) {
return new Promise((resolve, reject) => {
se (tipo de entrada === "número") {
Saída const = entrada * 2
resolve(output)
} else {
const error = novo erro ("Entrada Inválida")
rejeitar (erro)
}
})
}
Como podemos ver, a palavra-chave 'async' nos permite escrever funções com aparência síncrona em programação assíncrona. Ela não só pode nos libertar de construir objetos 'Promise' toda vez que quisermos retornar uma Promise em uma função, mas também pode nos ajudar a evitar incluir funções desconhecidas como 'resolve' e 'rejeitar' em nossa base de código.
Tentativa/Captura: Você pode até lidar com erros em um estilo síncrono
Neste ponto, você pode perguntar: "Como podemos lidar com erros sem usar o método de captura?" A resposta é surpreendentemente simples! Podemos usar nosso velho amigo — o construto 'tentar/capturar'.
função async multiplyByTwo(input) {
se (tipo de entrada === "número") {
Saída const = entrada * 2
Retorno de saída
} else {
const error = novo erro ("Entrada Inválida")
erro de arremesso
}
}
Função assíncrona main() {
try {
const output = await multiplyByTwo("2")
console.log(saída)
} captura (e) {
console.log(e)
}
}
main()
Para lidar com o erro que 'multiplyByTwo' pode potencialmente gerar, envolvemos sua invocação em um bloco 'try' e definimos as operações de tratamento de erros em um bloco 'catch' (no nosso caso, apenas exibimos o erro).
Assync/Await: Liberte-se das Correntes de Promessas
Em seguida, vou demonstrar como podemos usar async/await para reescrever o seguinte exemplo de código que utiliza Promise Chaining.
função addExclamationMark(entrada) {
return new Promise((resolve, reject) => {
se (tipo de entrada === "string") {
const output = entrada + "!"
resolve(output)
} else {
const error = novo erro ("Entrada Inválida")
rejeitar (erro)
}
})
}
addExclamationMark("Olá, mundo")
.then(addExclamationMark) // igual a .then((input) => addExclamationMark(input))
.then(addExclamationPoint)
.then(addExclamationPoint)
.then(addExclamationPoint)
.then((output) => console.log(output))
.catch((erro) => console.log(erro))
Primeiro adicionamos a palavra-chave 'async' à frente da definição de 'addExclamationMark'. Depois, removemos a construção do novo objeto 'Promise' para desembrulhar as operações principais. Depois disso, substituimos as invocações 'resolve' e 'reject' por uma instrução 'return' e uma 'throw', respectivamente.
função assíncrona addExclamationMark(input) {
se (tipo de entrada === "string") {
const output = entrada + "!"
Retorno de saída
} else {
const error = novo erro ("Entrada Inválida")
erro de arremesso
}
}
Função assíncrona main() {
try {
const output1 = await addExclamationMark("Olá, mundo")
const output2 = aguardar addExclamationMark(output1)
const output3 = aguardar addExclamationMark(output2)
const output4 = aguardar addExclamationMark(output3)
const output5 = aguardar addExclamationMark(output4)
console.log(saída5)
} catch (erro) {
console.log(erro)
}
}
main()
Para substituir a cadeia de promessas, separamos completamente as cinco invocações de 'addExclamationMark'. Colocamos 'await' antes de cada invocação de 'addExclamationMark' para atribuir a saída a uma nova constante. Além disso, passamos a saída da invocação anterior de 'addExclamationMark' como argumento para a próxima. Por fim, exibimos a saída final chamando 'console.log(output5)'.
Quanto às operações de tratamento de erros anteriormente incluídas no método 'catch', agora as colocamos em um bloco 'catch'. Seguindo o bloco 'catch' é um bloco 'try' contendo todas as operações que queremos realizar caso nada dê errado, incluindo as cinco invocações de 'addExclamationMark'.
Você pode ter o melhor dos dois mundos
Para quem não quer se comprometer totalmente com o uso do 'async/await', aqui vai uma boa notícia: você PODE ter o melhor dos dois mundos! Digamos que você esteja convencido da ideia de que usar 'async' é uma forma melhor de definir funções assíncronas (funções que retornam um objeto 'Promise'). No entanto, você não está disposto a perder sua liberdade de usar o Promise Chaining. Em vez de inundar relutantemente seu código com a palavra-chave 'await', você pode usar 'async' com o Promise Chaining. Você pode até misturar 'await' com promise chains, se for o estilo que você deseja. O mundo é seu!
Neste artigo, apresentei os fundamentos do 'async/await' — uma alternativa poderosa à forma usual de organizar a execução sequencial de operações (Promise Chaining) em programação assíncrona. Primeiro, expliquei como usar 'async/await' e as condições para usá-los. Depois, demonstrei como reescrever código assíncrono com 'async/await'. Por fim, mencionei que é totalmente aceitável usar 'async' e 'await' separadamente e seletivamente, sem compromisso absoluto. Espero que este artigo tenha inspirado você a começar a usar, ou pelo menos considerar usar, 'async' e 'await'.