Asynchronous JavaScript Programming: A Guide to Promises, Async/Await, and Generators

📆 · ⏳ 3 min read · ·

Introduction

Asynchronous programming is a crucial concept in JavaScript, especially when dealing with time-consuming tasks like fetching data from an API or processing large files. It allows the code to run non-blocking, so other tasks can execute without waiting for the previous task to complete.

In this article, we will dive into the some concepts of asynchronous programming in JavaScript, including Promises, Async/Await, and Generators.

Promises

Promises are a powerful tool for managing asynchronous operations in JavaScript. They allow us to handle success and failure cases of an operation in a more organized manner.

A promise represents a value that may not be available yet but will be resolved at some point in the future.

Promises provide two methods, then() and catch(), to handle the resolution and rejection of the promise.

Here’s an example of a simple promise that fetches data from an API:

fetch('https://jsonplaceholder.typicode.com/users/1')
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.log(error));

Copy and paste the above code in your browser console and you’ll notice this line get’s output immediately Promise { <state>: "pending" } and within a second or two the actual response from the API will appear in the console.

Async/Await

When promises were introduced in ES6, they made asynchronous programming much easier. However, they still had some limitations, such as the inability to use try/catch blocks and the lack of support for asynchronous operations inside then() and catch() methods.

Async/Await is a syntax improvement for writing asynchronous code in a synchronous style. It is built on top of Promises and provides a cleaner syntax for handling asynchronous operations.

The async keyword is used to define a function that returns a Promise, and the await keyword is used to wait for a Promise to resolve.

Here’s an example of an Async/Await function that fetches data from an API:

async function fetchData() {
try {
const response = await fetch(
'https://jsonplaceholder.typicode.com/users/1',
);
const data = await response.json();
console.log(data);
} catch (error) {
console.log(error);
}
}
fetchData();

Generators

Generators are functions that can be paused and resumed, allowing for more control over the execution of asynchronous operations. They use the yield keyword to pause the execution of a function and return a value.

Generators can be used to write asynchronous code that looks synchronous, making it easier to reason about. Read more in detail about Generators in another blog that I shared sometime back

Here’s an example of a Generator function that performs an asynchronous task:

function* fetchMultipleUrls(urls) {
for (const url of urls) {
try {
const response = yield fetch(url);
const data = yield response.json();
console.log(`Data for ${url}:`, data);
} catch (error) {
console.log(`Error for ${url}:`, error);
}
}
}
const urls = [
'https://jsonplaceholder.typicode.com/users/1',
'https://jsonplaceholder.typicode.com/users/2',
'https://jsonplaceholder.typicode.com/users/3',
];
function runGenerator(generator, prevValue) {
const { value, done } = generator.next(prevValue);
if (done) {
return;
}
if (value instanceof Promise) {
value
.then((result) => runGenerator(generator, result))
.catch((error) => {
generator.throw(error);
runGenerator(generator);
});
} else {
runGenerator(generator, value);
}
}
const generator = fetchMultipleUrls(urls);
runGenerator(generator);

In this example, the fetchMultipleUrls generator function takes an array of URLs as input and fetches data from each URL one by one. Each yield statement waits for the previous Promise to resolve before continuing to the next line of code.

The runGenerator function starts the generator and recursively calls itself until all URLs are processed. This ensures that the asynchronous tasks are executed sequentially, and the output is logged in the correct order.

Conclusion

Asynchronous programming is an essential skill for every JavaScript developer. Understanding Promises, Async/Await, and Generators can help you write more efficient and readable code.

With this guide, you should be able to understand the concepts of asynchronous programming in JavaScript and take your coding skills to the next level.

You may also like

  • Write Secure JavaScript Applications

    Dive into the realm of writing secure JavaScript applications. Uncover practical strategies to shield your web apps from XSS and CSRF vulnerabilities, ensuring robust and safe software in an interconnected world.

  • Multi-Threaded JavaScript with Web Workers

    Are you tired of slow and unresponsive web applications? Do you want to improve the performance of your JavaScript code without sacrificing user experience? Look no further than JavaScript's Web Workers API. In this article, we'll explore the basics of web workers and how they can help you create multi-threaded web applications.

  • Event Handling in JavaScript: Understanding Delegation and Propagation

    Event handling is an essential part of JavaScript programming. Whether it's a simple click event or a complex user interface interaction, understanding how events work is crucial to writing effective and efficient code. In this article, we'll explore advanced event handling concepts in JavaScript, including delegation and propagation. By mastering these concepts, you'll be able to write more maintainable and scalable code.