DEV Community

Lucas Pereira de Souza
Lucas Pereira de Souza

Posted on

Converting RxJS Observables to Async/Await

logotech

## Comparing Reactive and Asynchronous Streams: Equivalent Transformations

In modern software development, the need to efficiently handle long-running operations and events is crucial. Two popular approaches to achieve this are asynchronous programming and reactive streams. Although both share the goal of improving responsiveness and scalability, they differ in their principles and implementations. This article explores the nuances of these approaches, comparing them and demonstrating equivalent transformations for better understanding.

Asynchronous Programming: The Basics

Asynchronous programming allows a program to start an operation without waiting for it to complete immediately. Instead, the program continues to execute other tasks and is later notified when the asynchronous operation is finished. This is usually achieved through callbacks, Promises, or async/await. Asynchronous programming is excellent for handling I/O (input/output) operations, such as network requests or file reading, where the wait time can be significant.

Reactive Streams: Reacting to Data

Reactive streams, on the other hand, is a more data-oriented approach. It focuses on how data is emitted, received, and transformed over time. A reactive stream is essentially a sequence of events that can be observed and reacted to. Frameworks like RxJava, RxJS, and Reactor provide tools to create, combine, and transform data streams declaratively. The main advantage of reactive streams is their ability to handle data that arrives continuously and in varying volumes, making it ideal for real-time systems and streaming data processing.

Comparing the Approaches

Feature Asynchronous Programming Reactive Streams
Focus Non-blocking execution of operations. Data streams and reactivity.
Data Model Generally works with single data or results. Operates on streams (sequences) of data.
Notification Callbacks, Promises, async/await. Observers and Subscriptions.
Complexity Less complex for simple operations. Can be more complex to understand at first.
Use Cases I/O, background tasks. Real-time systems, streaming data.
Main Advantage Improved responsiveness. Flexibility in handling data streams.

Equivalent Transformations: Asynchronous vs. Reactive

Despite their differences, it is possible to find equivalencies between asynchronous operations and transformations in reactive streams. Let's consider some examples:

  1. Single Asynchronous Call (Promise) and Reactive Stream:
*   **Asynchronous (JavaScript with Promises):**
Enter fullscreen mode Exit fullscreen mode
    ```javascript
    async function fetchData() {
      const response = await fetch('https://api.example.com/data');
      const data = await response.json();
      console.log(data);
    }
    fetchData();
    ```
Enter fullscreen mode Exit fullscreen mode
*   **Reactive (RxJS):**
Enter fullscreen mode Exit fullscreen mode
    ```javascript
    import { fromFetch } from 'rxjs/operators';
    import { map, tap } from 'rxjs/operators';

    fromFetch('https://api.example.com/data')
      .pipe(
        switchMap(response => {
          if (response.ok) {
            return response.json();
          } else {
            return of({ error: response.status });
          }
        }),
        tap(data => console.log(data))
      )
      .subscribe();
    ```
Enter fullscreen mode Exit fullscreen mode
In this example, the asynchronous `fetch` call is equivalent to a reactive stream that emits a single value after the request is completed.
Enter fullscreen mode Exit fullscreen mode
  1. Mapping (Map) and Data Processing:
*   **Asynchronous (JavaScript with `Promise.all`):**
Enter fullscreen mode Exit fullscreen mode
    ```javascript
    async function processData() {
      const results = await Promise.all([
        fetch('https://api.example.com/data1').then(res => res.json()),
        fetch('https://api.example.com/data2').then(res => res.json())
      ]);
      const mappedResults = results.map(item => item.value * 2);
      console.log(mappedResults);
    }
    processData();
    ```
Enter fullscreen mode Exit fullscreen mode
*   **Reactive (RxJS):**
Enter fullscreen mode Exit fullscreen mode
    ```javascript
    import { forkJoin } from 'rxjs';
    import { map } from 'rxjs/operators';

    forkJoin({
      data1: fromFetch('https://api.example.com/data1').pipe(switchMap(response => response.json())),
      data2: fromFetch('https://api.example.com/data2').pipe(switchMap(response => response.json()))
    })
    .pipe(
      map(results => [results.data1.value * 2, results.data2.value * 2])
    )
    .subscribe(mappedResults => console.log(mappedResults));
    ```
Enter fullscreen mode Exit fullscreen mode
Both examples demonstrate how to transform received data. In the asynchronous approach, `Promise.all` and `map` are used; in the reactive approach, `forkJoin` (to combine streams) and `map` are used.
Enter fullscreen mode Exit fullscreen mode

Conclusion

Both asynchronous programming and reactive streams are powerful tools for building modern applications. The choice between them depends on the specific requirements of the project. Asynchronous programming is suitable for simple tasks and where responsiveness is crucial. Reactive streams shine in scenarios involving continuous data streams and complex transformations. Understanding the equivalencies between these approaches allows you to take full advantage of their strengths and create robust and efficient applications. The combination of these techniques is often the key to success in complex systems.

Top comments (0)