Having Fun with TypeScript: Promises, Async, Await

Async Programming Made Easy

Dagang Wei
3 min readMar 19, 2024
Source

This blog post is part of the series Having Fun with TypeScript.

Introduction

In the world of web development, managing asynchronous operations is crucial. Applications often need to fetch data from servers, handle user interactions that trigger delayed responses, or perform time-consuming tasks. TypeScript, being a superset of JavaScript, offers the ‘async/await’ syntax to streamline asynchronous programming, making our code more readable and maintainable.

In this blog post, we’ll delve into the fundamentals of async/await in TypeScript, drawing a clear distinction from traditional Promise-based approaches and emphasizing robust error handling.

The Foundation: Promises

Before we dive into async/await, it’s essential to grasp the concept of Promises in JavaScript. A Promise represents the eventual outcome of an asynchronous operation. It exists in one of three states:

  • Pending: The operation is ongoing.
  • Fulfilled: The operation completed successfully.
  • Rejected: The operation failed due to an error.

Let’s illustrate promises with a real-world example:

function fetchData(url: string): Promise<string> {
return new Promise((resolve, reject) => {
// Simulate an asynchronous network request
setTimeout(() => {
if (Math.random() > 0.5) {
resolve("Data fetched successfully!");
} else {
reject(new Error("Network error"));
}
}, 1000);
});
}

Promises with .then and .catch

Traditionally, we work with Promises using their .then and .catch methods:

fetchData("https://api.example.com")
.then((data) => {
console.log(data); // "Data fetched successfully!"
})
.catch((error) => {
console.error(error); // Error: Network error
});

Enter Async/Await

Async/await provides a cleaner way to work with Promises. Let’s refactor our example:

  • async: The async keyword declares that a function is asynchronous and always returns a Promise.
  • await: The await keyword pauses the execution of an async function until a Promise settles (either resolves or rejects). It can only be used within an async function.
async function fetchDataWithAsyncAwait(url: string): Promise<string> {
try {
const response = await fetch(url);
const data = await response.text();
return data;
} catch (error) {
throw new Error(`Error fetching data: ${error}`);
}
}

Notice how the async/await version reads like synchronous code, even though it’s handling asynchronous operations behind the scenes.

Error Handling

Error handling is paramount in asynchronous programming. With async/await, we employ the familiar try...catch block:

async function getUserData() {
try {
const userData = await fetchDataWithAsyncAwait("https://api.example.com/users/1");
console.log(userData);
} catch (error) {
console.error("Failed to get user data:", error);
}
}

Key Points

  • Async/await is syntactic sugar built on top of Promises.
  • Async functions always return a Promise.
  • Use await to pause execution within an async function until a Promise settles.
  • Employ try...catch blocks for robust error handling.

In Summary

The promises, async and await in TypeScript offers a powerful and elegant approach to managing asynchronous operations. By transforming the Promise-based paradigm into a more synchronous-like style, it enhances code readability, maintainability, and reduces the potential for errors common in callback-heavy Promise chains. For any TypeScript developer aiming to create robust, responsive, and user-friendly web applications, understanding async/await is an indispensable skill.

--

--