In modern JavaScript, asynchronous programming is essential for handling tasks like fetching data from an API, reading a file, or any other operation that takes time to complete. TypeScript, as a superset of JavaScript, offers robust support for handling asynchronous operations using Promises and the more recent async/await syntax.
Asynchronous programming is a programming paradigm that enables tasks to be executed concurrently, allowing programs to continue executing while waiting for certain operations to complete. This is particularly useful for I/O-bound tasks such as fetching data from a server or reading files.
Callbacks are a common mechanism for handling asynchronous operations in JavaScript and TypeScript. A callback function is passed as an argument to an asynchronous function and is executed once the operation completes.
function fetchData(callback: (data: any) => void) {
setTimeout(() => {
const data = 'Async Data';
callback(data);
}, 1000);
}
fetchData((data) => {
console.log('Data received:', data);
});
fetchData
function simulates an asynchronous operation using setTimeout
.A Promise is an object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value. Promises allow you to handle asynchronous operations in a more elegant way than using callbacks.
A Promise can be in one of three states:
Here’s how you create a basic promise in TypeScript:
let myPromise: Promise = new Promise((resolve, reject) => {
let success = true;
if (success) {
resolve("Operation was successful!");
} else {
reject("Operation failed.");
}
});
In the example above, the promise will call either the resolve
function (indicating success) or the reject
function (indicating failure) depending on the logic inside.
.then()
and .catch()
To handle the result of a promise, you can use .then()
for success and .catch()
for errors.
myPromise
.then((message: string) => {
console.log(message); // Output: Operation was successful!
})
.catch((error: string) => {
console.log(error); // If rejected, this will log: Operation failed.
});
Let’s expand on this with an example that mimics a network request.
function fetchData(): Promise {
return new Promise((resolve, reject) => {
setTimeout(() => {
let success = true;
if (success) {
resolve("Data fetched successfully!");
} else {
reject("Failed to fetch data.");
}
}, 2000); // Simulate a 2-second delay
});
}
fetchData()
.then((data) => console.log(data))
.catch((error) => console.error(error));
Data fetched successfully!
fetchData()
returns a promise that resolves after 2 seconds. If success
is true, it resolves with the message “Data fetched successfully!”. Otherwise, it rejects with “Failed to fetch data.”.then()
method handles the successful resolution, and .catch()
deals with any errors.The Promise.all()
method allows you to run multiple promises in parallel and wait until all of them are resolved or any one of them is rejected. This is useful when you need to wait for multiple asynchronous operations to complete.
let promise1 = Promise.resolve(100);
let promise2 = Promise.resolve(200);
let promise3 = Promise.resolve(300);
Promise.all([promise1, promise2, promise3])
.then((values) => {
console.log(values); // Output: [100, 200, 300]
});
Promise.race()
runs multiple promises concurrently but only returns the result of the first one that settles (whether it’s resolved or rejected).
let slowPromise = new Promise((resolve) => setTimeout(resolve, 5000, "Slow"));
let fastPromise = new Promise((resolve) => setTimeout(resolve, 1000, "Fast"));
Promise.race([slowPromise, fastPromise])
.then((value) => {
console.log(value); // Output: Fast (since the fast promise resolves first)
});
Async/Await is a modern syntax that simplifies working with promises. Instead of using .then()
and .catch()
, you can write asynchronous code that looks like synchronous code. async
functions always return a promise, and await
pauses the execution of the function until the promise is resolved or rejected.
Let’s rewrite the earlier fetchData
example using async/await.
async function fetchDataAsync(): Promise {
return new Promise((resolve, reject) => {
setTimeout(() => {
let success = true;
if (success) {
resolve("Data fetched successfully!");
} else {
reject("Failed to fetch data.");
}
}, 2000);
});
}
async function getData() {
try {
const data = await fetchDataAsync();
console.log(data); // Output: Data fetched successfully!
} catch (error) {
console.error(error);
}
}
getData();
async function
: Marks a function as asynchronous, meaning it returns a promise.await
: Pauses execution inside the getData
function until the fetchDataAsync()
promise is resolved or rejected.try/catch
: Used to handle errors in async/await, replacing .catch()
in promises.
Data fetched successfully!
Error handling in async/await is done using try/catch
blocks, making it more intuitive than using .catch()
with promises.
async function faultyFetchData(): Promise {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("Failed to fetch data.");
}, 1000);
});
}
async function getDataWithErrorHandling() {
try {
const data = await faultyFetchData();
console.log(data);
} catch (error) {
console.error("Error:", error); // Output: Error: Failed to fetch data.
}
}
getDataWithErrorHandling();
faultyFetchData()
function, the promise is rejected after 1 second.catch
block in getDataWithErrorHandling()
catches the error and logs it.
Error: Failed to fetch data.
One common misconception is that using await
runs all asynchronous functions sequentially. However, you can still run them concurrently by using Promise.all()
.
async function getDataConcurrently() {
const promise1 = fetchDataAsync();
const promise2 = fetchDataAsync();
const [result1, result2] = await Promise.all([promise1, promise2]);
console.log(result1, result2);
}
getDataConcurrently();
Promise.all()
allows both promises to be resolved concurrently, reducing total execution time.
Data fetched successfully! Data fetched successfully!
Just like promises, you can chain multiple asynchronous operations with async/await, making it easier to handle dependent operations.
async function getBookDetails() {
const book = await fetchBook();
const author = await fetchAuthor(book.authorId);
const reviews = await fetchReviews(book.id);
console.log(`Book: ${book.title}, Author: ${author.name}, Reviews: ${reviews.length}`);
}
getBookDetails();
In this example, fetchBook
, fetchAuthor
, and fetchReviews
are all asynchronous operations that depend on the result of the previous operation.
We explored Promises and async/await in TypeScript, from basic concepts to advanced techniques. Promises provide a cleaner way to handle asynchronous tasks than traditional callbacks, and async/await simplifies this further by making asynchronous code look synchronous. Happy coding !❤️