leanmind logo leanmind text logo

Blog

Refactorización Avanzada

Dominar el refactoring productivo para maximizar el retorno de la inversión.

La asincronia un viaje desde futures a promesas

Por Aitor Santana

En el desarrollo de software moderno, la asincronía es un pilar esencial, sobre todo en aplicaciones web en las que dependemos de la respuesta de APIs o servicios externos. Aquellos que estamos acostumbrados a programar web en JavaScript/TypeScript, conocemos las Promesas, que nos permiten manejar esas situaciones asíncronas que mencionaba antes. Sin embargo, estos últimos meses que he estado trabajando más con Java y Spring me surgió la duda, ¿habrá algo similar para este lenguaje? La respuesta es que sí y se llama Future.

Futures en Java: La Base de la Asincronía

Java, un lenguaje conocido por su robustez y capacidad de manejo de múltiples hilos, introduce la interfaz Future en Java 5 para representar resultados de operaciones asíncronas. Sin embargo, Future presenta limitaciones, especialmente por su naturaleza bloqueante y la falta de flexibilidad para manejar cadenas de operaciones o errores de forma eficiente. Es por eso que en Java 8 fue introducido CompletableFuture, una clase que implementa Future y CompletionStage y aborda muchas de las limitaciones de Future.

CompletableFuture

CompletableFuture proporciona una API rica para componer, combinar y manejar operaciones asíncronas, ofreciendo métodos como thenApply, thenCombine, y exceptionally para poder manejar de una forma más flexible los diferentes estados asíncronos. Entremos en más detalle sobre los métodos principales que tiene esta estructura:

Veámos un ejemplo de como podríamos hacer una petición http con esta estructura:

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class AsyncHttpRequest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://rickandmortyapi.com/api/character"))
                .build();

        CompletableFuture<HttpResponse<String>> responseFuture =
                client.sendAsync(request, HttpResponse.BodyHandlers.ofString());

        responseFuture
                .thenApply(HttpResponse::body)
                .thenAccept(System.out::println)
                .exceptionally(e -> {
                    System.out.println("Hubo un error en la petición: " + e.getMessage());
                    return null;
                })
                .join(); // Espera a que se complete la operación
    }
}

Existe otro método muy interesante, thenCombine que permite combinar dos CompletableFutures. Realiza dos operaciones de manera independiente y luego, combina sus resultados una vez que ambas se han completado, en una función que le pasemos como segundo parámetro. Veamoslo con un ejemplo:

import java.util.concurrent.CompletableFuture;

public class ThenCombineExample {
    public static void main(String[] args) {
        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            // Simula una tarea que calcula el cuadrado
            return 2 * 2; // 4
        });

        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            // Simula una tarea que calcula el cubo
            return 3 * 3 * 3; // 27
        });

        CompletableFuture<Integer> combinedFuture = future1.thenCombine(future2, (square, cube) -> {
            // Combina los resultados (4 + 27)
            return square + cube;
        });

        combinedFuture.thenAccept(total -> System.out.println("Total: " + total)); // Muestra "Total: 31"
    }
}

Promesas en JavaScript

En JavaScript la asincronía se maneja a través de Promesas. Introducidas como una mejora sobre los callbacks anidados en ES6 del año 2015. Las Promesas proporcionan una forma más limpia y manejable de organizar operaciones asíncronas. Con métodos como then, catch, y finally, así como las palabras reservadas async/await, que nos permiten simplificar el tratamiento de operaciones asíncronas y la gestión de errores.

Veamos el ejemplo de petición anterior en JavaScript:

// Sólo con Promesas
fetch("https://rickandmortyapi.com/api/character")
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((error) => console.error("Error en la petición:", error));

// Con async/await
try {
  const response = await fetch("https://rickandmortyapi.com/api/character");
  const characters = await response.json();
} catch (error) {
  console.error("Error en la petición:", error);
}

CompletableFuture VS Promesas

Ambas estructuras tienen un estilo bastante similar, su objetivo es el mismo, sin embargo difieren en 3 puntos importantes:

  1. Funcionalidades y Métodos: CompletableFuture ofrece una gama más amplia de métodos para el manejo de asincronía, incluyendo combinación y composición de múltiples Futures, además de la ejecución asíncrona. Las Promesas en JavaScript se centran en simplificar el flujo de operaciones asíncronas y el manejo de errores.
  2. Manejo de Errores: En Java, el manejo de errores con CompletableFuture puede ser más detallado, aprovechando las capacidades de manejo de excepciones de Java. En JavaScript, el manejo de errores con Promesas es más sencillo, utilizando principalmente .catch().
  3. Concurrencia: Java, con su modelo de concurrencia basado en hilos, permite un control más detallado sobre el paralelismo y la asincronía. En JavaScript, esto se maneja a través del Event Loop, que está enfocado en ser no bloqueante y en el uso eficiente del único hilo.

Conclusión

En resumen, el viaje desde CompletableFuture en Java hasta las Promesas en JavaScript, nos revela cómo lenguajes diferentes abordan la asincronía de formas únicas, cada uno con sus fortalezas. Mientras Java ofrece un enfoque robusto y detallado, ideal para aplicaciones con fuerte tipado y concurrencia, JavaScript brinda una solución elegante y adaptable, perfecta para el dinamismo de la web. Esta exploración, no solo enriquece nuestra caja de herramientas como desarrolladores, sino que también nos invita a reflexionar: ¿Cómo pueden estos paradigmas influir en nuestra forma de pensar y resolver problemas en la programación? Al final, comprender estas diferencias y similitudes no sólo nos hace mejores en un lenguaje, sino más adaptativos y hábiles en el arte de la programación.

Publicado el 10/01/2025 por

¿Quieres más? te invitamos a suscribirte a nuestro boletín para avisarte cada vez que recopilemos contenido de calidad que compartir.

Si disfrutas leyendo nuestro blog, ¿imaginas lo divertido que sería trabajar con nosotros? ¿te gustaría?

Impulsamos el crecimiento profesional de tu equipo de developers