Clase 122 — Asíncrono: async/await y promesas

Parte 7 — Paradigmas · ⏱️ Duración estimada: 90 min · Nivel: IntermedioClase construida — 10 implementaciones del núcleo verificadas contra casos.json.


🎯 Objetivo

La clase anterior repartía trabajo entre hilos que corrían de verdad a la vez. El paradigma asíncrono persigue un objetivo parecido —no quedarse parado esperando— pero con una filosofía opuesta: un solo hilo que, en vez de bloquearse mientras aguarda una operación lenta, la inicia, la deja en marcha y sigue haciendo otra cosa, para retomarla cuando el resultado esté listo. No hay paralelismo; hay concurrencia sin paralelismo. La palabra clave es no bloquear: cuando pides un dato a la red o al disco, no congelas el programa entero durante los milisegundos (o segundos) que tarda la respuesta.

La pieza que representa "un valor que todavía no está pero llegará" es la promesa (o future, o task). Es un objeto que devuelves de inmediato como un pagaré: "aquí tienes un recibo; cuando el cálculo termine, contendrá el valor". El operador await es lo que canjeas ese recibo: suspende tu función en ese punto, devuelve el control al bucle de eventos —el mismo motor que viste en la clase 119— y reanuda tu función justo donde la dejaste cuando la promesa se resuelve. Bajo el azúcar, async/await es una máquina de estados: el compilador trocea tu función en los puntos de await y guarda dónde iba para poder continuar después. Es, en el fondo, azúcar sintáctico sobre continuaciones y callbacks: lo que en la clase 119 escribías como pirámide de callbacks anidados, aquí se lee como código secuencial de arriba abajo.

Nuestro laboratorio reduce el paradigma a su gesto mínimo —una tarea asíncrona que calcula el doble de un número y se espera su valor— para que veas la forma async/await sin el ruido de la I/O real. El cálculo es instantáneo, pero la estructura (definir una operación asíncrona, iniciarla, esperar su resultado con await) es idéntica a la de leer un archivo o consultar una base de datos, que es donde este estilo despliega toda su ventaja.

📚 Resultados de aprendizaje

Al finalizar, podrás:

  1. Definir y esperar una tarea asíncrona.
  2. Explicar por qué no bloquea.
  3. Reconocer async/await por lenguaje.

🗺️ Temas

# Tema Por qué importa
1 Asíncrono No bloquear mientras se espera
2 async/await Esperar sin bloquear el hilo
3 Tarea/promesa/future El resultado que llegará

📖 Definiciones y características

🧩 Situación

Leer de la red, de disco o de una base de datos tarda. En vez de bloquear el hilo, async/await inicia la operación y continúa, esperando el resultado cuando llega. Clave en servidores y UI.

🧮 Modelo

Especificación y verificación en casos.json:

stdin esperado
5 resultado=10
0 resultado=0
6 resultado=12

📐 Algoritmo (pseudocódigo neutral)

async doble(x): DEVOLVER 2x ; resultado <- await doble(n)

🌐 Implementaciones idiomáticas — el código a la vista

Mismo algoritmo, forma idiomática en cada lenguaje. Todas producen la salida de casos.json. Cada bloque es el archivo real de implementaciones/: el enlace de cada lenguaje abre su fuente, y el comando de al lado lo ejecuta.

Python · python/main.py · python main.py

import sys
import asyncio


async def doble(x):
    return x * 2


async def main():
    n = int(sys.stdin.readline())
    resultado = await doble(n)
    print(f"resultado={resultado}")


asyncio.run(main())

🧬 El mismo programa en la familia Scripting dinámico: Ruby · Perl · Lua · Tcl · R

JavaScript · javascript/main.mjs · node main.mjs

import { readFileSync } from "node:fs";

async function doble(x) {
  return x * 2;
}

const n = parseInt(readFileSync(0, "utf8").trim(), 10);
(async () => {
  const resultado = await doble(n);
  console.log(`resultado=${resultado}`);
})();

🧬 El mismo programa en la familia JavaScript / web: Dart · ActionScript

TypeScript · typescript/main.ts · pnpm exec tsx main.ts

import { readFileSync } from "node:fs";

async function doble(x: number): Promise<number> {
  return x * 2;
}

const n: number = parseInt(readFileSync(0, "utf8").trim(), 10);
(async () => {
  const resultado: number = await doble(n);
  console.log(`resultado=${resultado}`);
})();

🧬 El mismo programa en la familia JavaScript / web: Dart · ActionScript

Java · java/Main.java · java Main.java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class Main {
    public static void main(String[] args) throws IOException, InterruptedException, ExecutionException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine().trim());
        CompletableFuture<Integer> tarea = CompletableFuture.supplyAsync(() -> n * 2);
        System.out.println("resultado=" + tarea.get());
    }
}

🧬 El mismo programa en la familia JVM: Kotlin · Scala · Groovy · Clojure

C# · csharp/Program.cs · dotnet run

using System;
using System.Threading.Tasks;

async Task<int> Doble(int x) => await Task.FromResult(x * 2);

int n = int.Parse(Console.In.ReadToEnd().Trim());
int resultado = await Doble(n);
Console.WriteLine($"resultado={resultado}");

🧬 El mismo programa en la familia .NET: F# · VB.NET

Go · go/main.go · go run main.go

package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
    "strings"
)

func main() {
    line, _ := bufio.NewReader(os.Stdin).ReadString('\n')
    n, _ := strconv.Atoi(strings.TrimSpace(line))
    // Go usa goroutines y canales en lugar de async/await.
    ch := make(chan int, 1)
    go func() { ch <- n * 2 }()
    fmt.Printf("resultado=%d\n", <-ch)
}

🧬 El mismo programa en la familia Sistemas: Zig · Nim · D

Rust · rust/main.rs · rustc main.rs -o main && ./main

use std::io::Read;

fn main() {
    let mut s = String::new();
    std::io::stdin().read_to_string(&mut s).unwrap();
    let n: i64 = s.trim().parse().unwrap();
    // Sin runtime async externo, se muestra el resultado de la tarea.
    let resultado = n * 2;
    println!("resultado={resultado}");
}

🧬 El mismo programa en la familia Sistemas: Zig · Nim · D

C · c/main.c · cc main.c -o main && ./main

#include <stdio.h>

int main(void) {
    long n;
    if (scanf("%ld", &n) != 1) return 1;
    /* C no tiene async a nivel de lenguaje; se calcula el resultado. */
    printf("resultado=%ld\n", n * 2);
    return 0;
}

🧬 El mismo programa en la familia C / llaves: C++ · Objective-C

SQL · sql/main.sql · sqlite3 :memory: < main.sql

-- SQL no tiene async a nivel de lenguaje.
WITH nums(n) AS (VALUES (5), (0), (6))
SELECT printf('resultado=%d', n * 2) AS resultado FROM nums;

🧬 El mismo programa en la familia Lógica y declarativa: Prolog · Datalog

PHP · php/main.php · php main.php

<?php
$n = (int) trim(fgets(STDIN));
// PHP es síncrono por defecto; se muestra el resultado de la tarea.
echo "resultado=" . ($n * 2) . "\n";

🧬 El mismo programa en la familia Scripting dinámico: Ruby · Perl · Lua · Tcl · R

SQL es declarativo: no lee de stdin como los demás; su implementación muestra la misma idea sobre una tabla de casos, y el verificador la marca como ilustrativa.

🔬 Comparación

Clase de diferencia Observación entre lenguajes
Sintáctica async/await (JS/TS/Python/C#/Rust), goroutines+canales (Go).
Semántica await no bloquea el hilo; libera para otras tareas.
Paradigmática SQL no tiene async a nivel de lenguaje.

🧬 El concepto en la familia

JavaScript popularizó async/await; hoy está en Python, C#, Rust y otros. Go usa goroutines en su lugar.

✅ Prueba común

Los mismos casos para todas las implementaciones: casos.json. Verifica la equivalencia:

python scripts/verificar_equivalencia.py 122

🧪 Reto de transferencia

Detalle en reto.md.

⚠️ Errores comunes

❓ Preguntas frecuentes

🔗 Referencias

Libros de la parte:

Libros de los lenguajes del núcleo:


⏮️ Clase 121 · 📂 Parte · 📚 Índice · 🌐 Atlas · Clase 123 ⏭️