Handling asynchronous code in JavaScript can become messy quickly. Nested callbacks, chained promises, and complex async workflows often make code difficult to maintain.
Whether you're dealing with API calls, user events, or real-time data streams, managing asynchronous data efficiently is critical.
In plain JavaScript, asynchronous logic is typically handled using:
- callbacks
- promises
- async/await
While these approaches work well for many situations, they have limitations, especially when working with multiple values over time. This is where RxJS and observables provide a powerful alternative.
Using Callbacks
Callbacks were one of the earliest ways to handle asynchronous operations in JavaScript. A typical example might look like this:
apiCall(function (response) {
processResponse(response, function (processedData) {
writeFile(processedData, function () {
console.log('File written successfully');
});
});
});
In this example, we first make an API call, process the returned data with a callback, and then write it to a file using another callback. While this works, the code can quickly become difficult to read, especially as more nested logic is introduced. This is often referred to as callback hell.
Using Promises
Promises improved asynchronous code significantly by providing a more readable interface compared to callbacks. Example:
apiCall()
.then(response => processResponse(response))
.then(processedData => writeFile(processedData))
.then(() => console.log('File written successfully'))
.catch(error => console.error('Error occurred:', error))
.finally(() => console.log('Operation complete'));
With promises, we can chain the then(), catch(), and finally() methods to handle the API response and subsequent actions. This is easier to read than nested callbacks, but it’s important to note that promises only handle a single value. Once the promise is resolved and the value is passed through the chain, the operation is done. This makes promises less suitable for scenarios where multiple values are produced over time.
Using Async/Await
The async/await syntax makes asynchronous code look very similar to synchronous code:
async function handleData() {
try {
const response = await apiCall();
const processedData = await processResponse(response);
await writeFile(processedData);
console.log('File written successfully');
} catch (error) {
console.error('Error occurred:', error);
} finally {
console.log('Operation complete');
}
}
handleData();
However, under the hood, async/await still relies on promises, which means it shares the same limitation - it works with a single resolved value.
Using RxJS and Observables
RxJS introduces the concept of observables, which allow us to work with streams of values over time rather than a single result.
const observable = apiCall$();
const observer = {
next: (value: any) => console.log('Received value:', value),
error: (error: Error) => console.error('Error occurred:', error),
complete: () => console.log('All values processed'),
};
observable.subscribe(observer);
In this code, we call a function that returns an observable. We then subscribe to this observable by passing an observer with three methods:
-
next()- called whenever a new value is produced -
error()- called if there’s an error during the data flow -
complete()- called when there are no more values to process
The key advantage of using RxJS is that an observable can emit multiple values over time, calling the next() method each time a new value is available. This makes it ideal for working with streams of data, such as user input events or real-time server responses.
Final Thoughts
Callbacks, promises, and async/await are all useful tools in JavaScript. However, when dealing with multiple asynchronous values over time, observables provide a far more flexible and powerful model. They allow developers to think in terms of data streams instead of individual events, which leads to cleaner and more maintainable asynchronous code.
If you want to explore the topic further, I cover RxJS in much greater depth in my book Mastering RxJS from Scratch: A Step-by-Step Guide to Understanding RxJS.
The book is designed as a practical, step-by-step guide that helps developers understand RxJS from the ground up, explains the most important operators, and demonstrates how to apply reactive programming in real-world applications.
Top comments (0)