A programação assíncrona é um paradigma de programação que permite escrever código que é executado assíncronamente
. Em contraste com a programação síncrona, que executa o código sequencialmente, a programação assíncrona permite que o código seja executado em segundo plano enquanto o restante do programa continua a ser executado. Isso é particularmente útil para tarefas que podem levar muito tempo para serem concluídas, como buscar dados de uma API remota.
A programação assíncrona
é essencial para criar aplicações responsivas e eficientes em JavaScript. TypeScript, um superconjunto de JavaScript, torna ainda mais fácil trabalhar com programação assíncrona.
Existem várias abordagens para programação assíncrona
em TypeScript, incluindo o uso de promessas
, async/await
e callbacks
. Vamos cobrir cada uma dessas abordagens em detalhes para que você possa escolher a(s) melhor(es) para o seu caso de uso.
Índice
Por que a Programação Assíncrona é Importante?
A programação assíncrona é crucial para construir aplicações web responsivas e eficientes. Ela permite que tarefas sejam executadas em segundo plano enquanto o restante do programa continua, mantendo a interface do usuário responsiva à entrada. Além disso, a programação assíncrona pode aumentar o desempenho geral ao permitir que várias tarefas sejam executadas ao mesmo tempo.
Há muitos exemplos do mundo real de programação assíncrona, como acessar câmeras e microfones de usuários e lidar com eventos de entrada do usuário. Mesmo que você não crie frequentemente funções assíncronas, é importante saber como usá-las corretamente para garantir que sua aplicação seja confiável e tenha bom desempenho.
Como o TypeScript Facilita a Programação Assíncrona
O TypeScript oferece várias funcionalidades que simplificam a programação assíncrona, incluindo segurança de tipo
, inferência de tipo
, verificação de tipo
e anotações de tipo
.
Com a segurança de tipo, você pode garantir que seu código se comporte conforme o esperado, mesmo ao lidar com funções assíncronas. Por exemplo, o TypeScript pode detectar erros relacionados a valores nulos e indefinidos em tempo de compilação, economizando tempo e esforço na depuração.
A inferência e verificação de tipo do TypeScript também reduzem a quantidade de código repetitivo que você precisa escrever, tornando seu código mais conciso e fácil de ler.
E as anotações de tipo do TypeScript fornecem clareza e documentação para o seu código, o que é especialmente útil ao trabalhar com funções assíncronas que podem ser complexas de entender.
Agora vamos mergulhar e aprender sobre essas três características principais da programação assíncrona: promessas, async/await e callbacks.
Como Usar Promessas em TypeScript
Promessas são uma ferramenta poderosa para lidar com operações assíncronas em TypeScript. Por exemplo, você pode usar uma promessa para buscar dados de uma API externa ou para realizar uma tarefa que consome tempo em segundo plano enquanto sua thread principal continua em execução.
Para usar uma Promessa, você cria uma nova instância da classe Promise
e passa uma função que executa a operação assíncrona. Essa função deve chamar o método resolve com o resultado quando a operação for bem-sucedida ou o método reject com um erro se falhar.
Uma vez que a Promessa é criada, você pode anexar callbacks a ela usando o método then
. Esses callbacks serão acionados quando a Promessa for cumprida, com o valor resolvido passado como um parâmetro. Se a Promessa for rejeitada, você pode anexar um manipulador de erros usando o método catch, que será chamado com a razão da rejeição.
Usar Promessas oferece várias vantagens em relação aos métodos tradicionais baseados em callbacks. Por exemplo, Promessas podem ajudar a prevenir o “inferno dos callbacks”, um problema comum em código assíncrono onde callbacks aninhados se tornam difíceis de ler e manter.
Promessas também facilitam o tratamento de erros em código assíncrono, pois você pode usar o método catch para gerenciar erros que ocorrem em qualquer lugar na cadeia de Promessas.
Finalmente, Promessas podem simplificar seu código ao fornecer uma maneira consistente e composta de lidar com operações assíncronas, independentemente de sua implementação subjacente.
Como Criar uma Promise
Sintaxe da Promise:
const myPromise = new Promise((resolve, reject) => {
// Faça alguma operação assíncrona
// Se a operação for bem-sucedida, chame o resolve com o resultado
// Se a operação falhar, chame o reject com um objeto de erro
});
myPromise
.then((result) => {
// Manipule o resultado bem-sucedido
})
.catch((error) => {
// Manipule o erro
});
// Exemplo 1 de como criar uma promise
function myAsyncFunction(): Promise<string> {
return new Promise<string>((resolve, reject) => {
// Alguma operação assíncrona
setTimeout(() => {
// Operação bem-sucedida resolve a promiseConfira minha última postagem no blog sobre domínio da programação assíncrona em TypeScript! Aprenda a trabalhar com Promises, Async/Await e Callbacks para escrever código eficiente e escalável. Prepare-se para levar suas habilidades em TypeScript para o próximo nível!
const success = true;
if (success) {
// Resolva a promise com o resultado da operação se a operação foi bem-sucedida
resolve(
`The result is success and your operation result is ${operationResult}`
);
} else {
const rejectCode: number = 404;
const rejectMessage: string = `The result is failed and your operation result is ${rejectCode}`;
// Rejeite a promise com o resultado da operação se a operação falhar
reject(new Error(rejectMessage));
}
}, 2000);
});
}
// Use a promise
myAsyncFunction()
.then((result) => {
console.log(result); // saída: O resultado é sucesso e o resultado da operação é 4
})
.catch((error) => {
console.error(error); // saída: O resultado é falha e o resultado da operação é 404
});
No exemplo acima, temos uma função chamada myAsyncFunction()
que retorna uma promise
. Usamos o construtor Promise
para criar a promise, que aceita uma função de callback
com os argumentos resolve
e reject
. Se a operação assíncrona for bem-sucedida, chamamos a função de resolve. Se falhar, chamamos a função de reject.
O objeto promise retornado pelo construtor possui um método then()
, que aceita funções de callback para sucesso e falha. Se a promise for resolvida com sucesso, a função de callback de sucesso é chamada com o resultado. Se a promise for rejeitada, a função de callback de falha é chamada com uma mensagem de erro.
O objeto promise também possui um método catch()
usado para lidar com erros que ocorrem durante a cadeia de promises. O método catch()
aceita uma função de callback, que é chamada se algum erro ocorrer na cadeia de promises.
Agora, vamos passar para como encadear promises em TypeScript.
Como Encadear Promises
Encadear promises permite que você execute várias operações assíncronas
em sequência ou em paralelo. Isso é útil quando você precisa realizar várias tarefas assíncronas uma após a outra ou ao mesmo tempo. Por exemplo, você pode precisar buscar dados de forma assíncrona e depois processá-los de forma assíncrona.
Vamos ver um exemplo de como encadear promises:
// Exemplo de como funciona encadear promessas
// Primeira promessa
const promise1 = new Promise((resolve, reject) => {
const functionOne: string = "This is the first promise function";
setTimeout(() => {
resolve(functionOne);
}, 1000);
});
// Segunda promessa
const promise2 = (data: number) => {
const functionTwo: string = "This is the second second promise function";
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(` ${data} '+' ${functionTwo} `);
}, 1000);
});
};
// Encadeando a primeira e a segunda promessas juntas
promise1
.then(promise2)
.then((result) => {
console.log(result); // saída: Esta é a primeira função de promessa + Esta é a segunda função de promessa
})
.catch((error) => {
console.error(error);
});
No exemplo acima, temos duas promessas: promessa1
e promessa2
. promessa1
é resolvida após 1 segundo com a string “Esta é a primeira função de promessa.” promessa2
recebe um número como entrada e retorna uma promessa que é resolvida após 1 segundo com uma string que combina o número de entrada e a string “Esta é a segunda função de promessa.”
Encadeamos as duas promessas usando o método then
. A saída promessa1
é passada como entrada para promessa2
. Por fim, usamos o método then
novamente para registrar a saída de promessa2
no console. Se tanto promessa1
quanto promessa2
rejeitarem, o erro será capturado pelo método catch
.
Parabéns! Você aprendeu como criar e encadear promessas no TypeScript. Agora, você pode usar promessas para realizar operações assíncronas no TypeScript. Agora, vamos explorar como o Async/Await
funciona no TypeScript.
Como Usar Async / Await no TypeScript
Async/await é uma sintaxe introduzida no ES2017 para facilitar o trabalho com Promises. Permite escrever código assíncrono que parece e se comporta como código síncrono.
No TypeScript, é possível definir uma função assíncrona usando a palavra-chave async
. Isso indica ao compilador que a função é assíncrona e retornará uma Promise.
Agora, vejamos como usar async/await no TypeScript.
Sintaxe Async/Await:
// Sintaxe Async/Await no TypeScript
async function functionName(): Promise<ReturnType> {
try {
const result = await promise;
// código a ser executado após a resolução da promise
return result;
} catch (error) {
// código a ser executado caso a promise seja rejeitada
throw error;
}
}
No exemplo acima, nomeDaFuncao
é uma função async que retorna uma Promise do tipo ReturnType
. A palavra-chave await
é usada para aguardar a resolução da promise antes de passar para a próxima linha de código.
O bloco try/catch
é utilizado para lidar com quaisquer erros que ocorram durante a execução do código dentro da função async. Se um erro ocorrer, ele será capturado pelo bloco catch, onde pode ser tratado adequadamente.
Usando Funções de Seta com Async/Await
Também é possível usar funções de seta com a sintaxe async/await no TypeScript:
const functionName = async (): Promise<ReturnType> => {
try {
const result = await promise;
// código a ser executado após a resolução da promise
return result;
} catch (error) {
// código a ser executado caso a promise seja rejeitada
throw error;
}
};
No exemplo acima, functionName
é definido como uma função de seta que retorna uma Promise de ReturnType
. A palavra-chave async indica que esta é uma função assíncrona, e a palavra-chave await é usada para esperar a resolução da promise antes de avançar para a próxima linha de código.
Async / Await com uma Chamada de API
Agora, vamos além da sintaxe e buscar alguns dados de uma API usando async/await.
interface User {
id: number;
name: string;
email: string;
}
const fetchApi = async (): Promise<void> => {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
if (!response.ok) {
throw new Error(
`Failed to fetch users (HTTP status code: ${response.status})`
);
}
const data: User[] = await response.json();
console.log(data);
} catch (error) {
console.error(error);
throw error;
}
};
fetchApi();
Aqui, estamos buscando dados da API JSONPlaceholder, convertendo-os em JSON e então exibindo-os no console. Este é um exemplo do mundo real de como usar async/await no TypeScript.
Você deverá ver informações do usuário no console. Esta imagem mostra a saída:
Async/Await com Chamada de API do Axios
// Exemplo 2 de como usar async/await no typescript
const fetchApi = async (): Promise<void> => {
try {
const response = await axios.get(
"https://jsonplaceholder.typicode.com/users"
);
const data = await response.data;
console.log(data);
} catch (error) {
console.error(error);
}
};
fetchApi();
No exemplo acima, definimos a função fetchApi()
usando async/await e o método Axios.get()
para fazer uma solicitação HTTP GET para a URL especificada. Usamos await para esperar a resposta, então extraímos os dados usando a propriedade data do objeto de resposta. Por fim, exibimos os dados no console com console.log()
. Quaisquer erros que ocorram são capturados e exibidos no console com console.error()
.
Podemos alcançar isso usando o Axios, então você deverá ver o mesmo resultado no console.
Esta imagem mostra a saída ao usar o Axios no console:
Nota: Antes de testar o código acima, você precisa instalar o Axios usando npm ou yarn.
npm install axios
yarn add axios
Se não estiver familiarizado com o Axios, você pode aprender mais sobre ele aqui.
Você pode ver que usamos um bloco try
e catch
para lidar com erros. O bloco try
e catch
é um método para gerenciar erros no TypeScript. Portanto, sempre que fizer chamadas de API como acabamos de fazer, certifique-se de usar um bloco try
e catch
para lidar com quaisquer erros.
Agora, vamos explorar um uso mais avançado do bloco try
e catch
no TypeScript:
// Exemplo 3 de como usar async / await no typescript
interface Recipe {
id: number;
name: string;
ingredients: string[];
instructions: string[];
prepTimeMinutes: number;
cookTimeMinutes: number;
servings: number;
difficulty: string;
cuisine: string;
caloriesPerServing: number;
tags: string[];
userId: number;
image: string;
rating: number;
reviewCount: number;
mealType: string[];
}
const fetchRecipes = async (): Promise<Recipe[] | string> => {
const api = "https://dummyjson.com/recipes";
try {
const response = await fetch(api);
if (!response.ok) {
throw new Error(`Failed to fetch recipes: ${response.statusText}`);
}
const { recipes } = await response.json();
return recipes; // Retorna a array de receitas
} catch (error) {
console.error("Error fetching recipes:", error);
if (error instanceof Error) {
return error.message;
}
return "An unknown error occurred.";
}
};
// Busca e exibe receitas
fetchRecipes().then((data) => {
if (Array.isArray(data)) {
console.log("Recipes fetched successfully:", data);
} else {
console.error("Error message:", data);
}
});
No exemplo acima, definimos uma interface Receita
que delineia a estrutura dos dados que esperamos da API. Em seguida, criamos a função fetchRecipes()
usando async/await e o método fetch() para fazer uma solicitação HTTP GET para o endpoint da API especificado.
Usamos um bloco try/catch
para lidar com quaisquer erros que possam ocorrer durante a solicitação à API. Se a solicitação for bem-sucedida, extraímos a propriedade de dados da resposta usando await e a retornamos. Se ocorrer um erro, verificamos a mensagem de erro e a retornamos como uma string, se existir.
Finalmente, chamamos a função fetchRecipes()
e usamos .then()
para registrar os dados retornados no console. Este exemplo demonstra como usar async/await
com blocos try/catch
para tratar erros em um cenário mais avançado, onde precisamos extrair dados de um objeto de resposta e retornar uma mensagem de erro personalizada.
Esta imagem mostra o resultado de saída do código:
Async / Await com Promise.all
Promise.all()
é um método que recebe um array de promessas como entrada (um iterável) e retorna uma única Promise como saída. Esta Promise é resolvida quando todas as promessas de entrada foram resolvidas ou se o iterável de entrada não contém promessas. Ela rejeita imediatamente se alguma das promessas de entrada for rejeitada ou se não-promessas lançarem um erro, e será rejeitada com a primeira mensagem de rejeição ou erro.
// Exemplo de uso de async / await com Promise.all
interface User {
id: number;
name: string;
email: string;
profilePicture: string;
}
interface Post {
id: number;
title: string;
body: string;
}
interface Comment {
id: number;
postId: number;
name: string;
email: string;
body: string;
}
const fetchApi = async <T>(url: string): Promise<T> => {
try {
const response = await fetch(url);
if (response.ok) {
const data = await response.json();
return data;
} else {
throw new Error(`Network response was not ok for ${url}`);
}
} catch (error) {
console.error(error);
throw new Error(`Error fetching data from ${url}`);
}
};
const fetchAllApis = async (): Promise<[User[], Post[], Comment[]]> => {
try {
const [users, posts, comments] = await Promise.all([
fetchApi<User[]>("https://jsonplaceholder.typicode.com/users"),
fetchApi<Post[]>("https://jsonplaceholder.typicode.com/posts"),
fetchApi<Comment[]>("https://jsonplaceholder.typicode.com/comments"),
]);
return [users, posts, comments];
} catch (error) {
console.error(error);
throw new Error("Error fetching data from one or more APIs");
}
};
fetchAllApis()
.then(([users, posts, comments]) => {
console.log("Users: ", users);
console.log("Posts: ", posts);
console.log("Comments: ", comments);
})
.catch((error) => console.error(error));
No código acima, usamos Promise.all
para buscar várias APIs ao mesmo tempo. Se você tiver várias APIs para buscar, pode usar Promise.all
para obtê-las todas de uma vez. Como você pode ver, usamos map
para percorrer o array de APIs e depois passá-lo para Promise.all
para buscá-las simultaneamente.
A imagem abaixo mostra a saída das chamadas de API:
Vamos ver como usar Promise.all
com Axios:
// Exemplo de uso de async / await com axios e Promise.all
const fetchApi = async () => {
try {
const urls = [
"https://jsonplaceholder.typicode.com/users",
"https://jsonplaceholder.typicode.com/posts",
];
const responses = await Promise.all(urls.map((url) => axios.get(url)));
const data = await Promise.all(responses.map((response) => response.data));
console.log(data);
} catch (error) {
console.error(error);
}
};
fetchApi();
No exemplo acima, estamos usando Promise.all
para buscar dados de dois URLs diferentes ao mesmo tempo. Primeiro, criamos um array de URLs, depois usamos o map para criar um array de Promises a partir das chamadas axios.get
. Passamos este array para Promise.all
, que retorna um array de respostas. Por fim, usamos o map novamente para obter os dados de cada resposta e exibi-los no console.
Como usar Callbacks em TypeScript
Um callback é uma função passada como argumento para outra função. A função de callback é executada dentro da outra função. Callbacks garantem que uma função não seja executada antes que uma tarefa seja concluída – mas que seja executada imediatamente após a conclusão da tarefa. Eles nos ajudam a escrever código JavaScript assíncrono e prevenir problemas e erros.
// Exemplo de uso de callbacks em TypeScript
const add = (a: number, b: number, callback: (result: number) => void) => {
const result = a + b;
callback(result);
};
add(10, 20, (result) => {
console.log(result);
});
A imagem abaixo mostra a função de callback:
Vamos ver outro exemplo de uso de callbacks em TypeScript:
// Exemplo de uso de uma função de callback em TypeScript
type User = {
name: string;
email: string;
};
const fetchUserData = (
id: number,
callback: (error: Error | null, user: User | null) => void
) => {
const api = `https://jsonplaceholder.typicode.com/users/${id}`;
fetch(api)
.then((response) => {
if (response.ok) {
return response.json();
} else {
throw new Error("Network response was not ok.");
}
})
.then((data) => {
const user: User = {
name: data.name,
email: data.email,
};
callback(null, user);
})
.catch((error) => {
callback(error, null);
});
};
// Uso de fetchUserData com uma função de callback
fetchUserData(1, (error, user) => {
if (error) {
console.error(error);
} else {
console.log(user);
}
});
No exemplo acima, temos uma função chamada fetchUserData
que recebe um id
e um callback
como parâmetros. Este callback
é uma função com dois parâmetros: um erro e um usuário.
A função fetchUserData
obtém dados do usuário de um endpoint da API JSONPlaceholder usando o id
. Se a busca for bem-sucedida, ela cria um objeto User
e o passa para a função de retorno com um erro nulo. Se houver um erro durante a busca, ele envia o erro para a função de retorno com um usuário nulo.
Para usar a função fetchUserData
com um retorno, fornecemos um id
e uma função de retorno como argumentos. A função de retorno verifica os erros e registra os dados do usuário se não houver erros.
A imagem abaixo mostra a saída das chamadas da API:
Como Usar Callbacks de Forma Responsável
Embora os callbacks sejam fundamentais para a programação assíncrona em TypeScript, eles exigem gerenciamento cuidadoso para evitar o “callback hell” – o código profundamente aninhado em forma de pirâmide que se torna difícil de ler e manter. Veja como usar callbacks de forma eficaz:
-
Avoid deep nesting
-
Desdobre a estrutura do seu código dividindo operações complexas em funções nomeadas
-
Use promises ou async/await para fluxos de trabalho assíncronos complexos (mais sobre isso abaixo)
-
-
Tratamento de erros primeiro
-
Siga sempre a convenção do Node.js de parâmetros
(error, result)
-
Verifique erros em todos os níveis de callbacks aninhados
-
function processData(input: string, callback: (err: Error | null, result?: string) => void) {
// ... sempre chame o callback com o erro primeiro
}
-
Use anotações de tipo
-
Utilize o sistema de tipos do TypeScript para impor assinaturas de callback
-
Defina interfaces claras para os parâmetros de callback
-
type ApiCallback = (error: Error | null, data?: ApiResponse) => void;
-
Considere bibliotecas de controle de fluxo
Para operações assíncronas complexas, use utilitários comoasync.js
para:-
Execução paralela
-
Execução em série
-
Tratamento de erros em pipelines
-
Quando usar callbacks vs. alternativas
Existem momentos em que callbacks são uma ótima escolha, e outros em que não são.
Callbacks são úteis quando você está trabalhando com operações assíncronas (completação única), interagindo com bibliotecas ou APIs mais antigas que esperam callbacks, lidando com ouvintes de eventos (como ouvintes de clique ou eventos de websocket) ou criando utilitários leves com necessidades assíncronas simples.
Em outros cenários em que você precisa se concentrar em escrever código manutenível com um fluxo assíncrono claro, callbacks causam problemas e você deve preferir promessas ou async-await. Por exemplo, quando você precisa encadear múltiplas operações, lidar com a propagação complexa de erros, trabalhar com APIs modernas (como a API Fetch ou FS Promises), ou usar promise.all()
para execução paralela.
Exemplo de migração de callbacks para promessas:
// Versão de retorno de chamada
function fetchUser(id: number, callback: (err: Error | null, user?: User) => void) {
// ...
}
// Versão de Promessa
async function fetchUserAsync(id: number): Promise<User> {
// ...
}
// Uso com async/await
try {
const user = await fetchUserAsync(1);
} catch (error) {
// Lidar com erro
}
A Evolução dos Padrões Assíncronos
Padrão | Prós | Contras |
Callbacks | Simples, universal | Complexidade aninhada |
Promessas | Encadeável, melhor fluxo de erro | Requer correntes .then() |
Async/Await | Legibilidade semelhante a sincronia | Requer transpilação |
Projetos modernos TypeScript frequentemente usam uma mistura: callbacks para padrões orientados a eventos e promessas/async-await para lógica assíncrona complexa. A chave é escolher a ferramenta certa para o seu caso de uso específico enquanto mantém a clareza do código.
Conclusão
Neste artigo, aprendemos sobre as diferentes maneiras de lidar com código assíncrono em TypeScript. Aprendemos sobre callbacks, promessas, async/await e como usá-los em TypeScript. Também aprendemos sobre esse conceito.
Se você quiser aprender mais sobre programação e como se tornar um melhor engenheiro de software, você pode se inscrever no meu canal do YouTube CliffTech.
Obrigado por ler meu artigo. Espero que você tenha gostado. Se você tiver alguma dúvida, fique à vontade para entrar em contato comigo.
Conecte-se comigo nas redes sociais: