Dall’Inferno delle Callback ad Async/Await: Storia del “Ponte” chiamato Promisification

Se programmi in JavaScript da un po’ di tempo, probabilmente consideri async/await come l’aria che respiri: una sintassi ovvia, pulita e lineare. Ma non è sempre stato così. C’è stata un’epoca in cui scrivere codice asincrono in JavaScript significava addentrarsi in una giungla di parentesi graffe e indentazioni selvagge, un luogo oscuro noto a tutti come Callback Hell.

In questo articolo faremo un viaggio nel tempo: vedremo come siamo fuggiti da quell’inferno, come è nato async/await

e come usare la Promisification per traghettare le vecchie API del browser nel mondo moderno.


1. Il punto di partenza: Il Callback Hell

In origine, JavaScript gestiva l’asincronia (come timer, richieste di rete o lettura di file) usando le callback: funzioni passate come argomento ad altre funzioni, da eseguire non appena l’operazione fosse terminata.

Il problema sorgeva quando dovevi eseguire diverse operazioni asincrone in sequenza (l’azione B dipende da A, l’azione C dipende da B). Il codice iniziava a spostarsi verso destra in una spaventosa struttura a piramide, rendendo i bug difficili da scovare e il codice impossibile da leggere.

Immaginiamo di dover simulare un flusso di autenticazione: recuperare un utente, poi i suoi post, e infine i commenti di un post. In passato si scriveva così:

javascript

// Benvenuti nell'inferno delle callback 
recuperaUtente(1, (utente) => {
    console.log("Utente recuperato:", utente.name);
    
    recuperaPostUtente(utente.id, (posts) => {
        console.log("Post recuperati:", posts.length);
        
        recuperaCommentiPost(posts[0].id, (commenti) => {
            console.log("Commenti recuperati:", commenti);
            // E se volessimo fare un'altra operazione? Altro livello!
        }, (erroreCommenti) => console.error(erroreCommenti));
    }, (errorePost) => console.error(errorePost));
}, (erroreUtente) => console.error(erroreUtente));

Questo nido di funzioni non è solo brutto da vedere, ma costringe anche a gestire gli errori in ogni singolo livello. Un incubo.


2. La rivoluzione: La nascita di Async/Await

Per risolvere questo problema, ES6 ha introdotto le Promise, e successivamente ES2017 ha rilasciato la ciliegina sulla torta: async/await.

Questa combinazione ha cambiato tutto. Invece di passare una funzione dentro l’altra, il codice asincrono ora restituisce un oggetto (la Promise) che promette di darti un risultato in futuro. Grazie ad await, puoi dire a JavaScript di fermarsi e aspettare quel risultato in modo lineare, come se fosse codice sincrono.

Il blocco sopra si trasforma magicamente in questo:

javascript

async function eseguiFlussoAutenticazione() {
    try {
        const utente = await recuperaUtente(1);
        const posts = await recuperaPostUtente(utente.id);
        const commenti = await recuperaCommentiPost(posts[0].id);
        console.log("Commenti recuperati:", commenti);
    } catch (errore) {
        // Un unico punto centralizzato per gestire TUTTI gli errori del flusso!
        console.error("Si è verificato un errore nel flusso:", error);
    }
}

3. Il Ponte: Cos’è la Promisification?

Qui arriviamo al problema concreto: le funzioni moderne accettano le Promise, ma il passato non scompare dall’oggi al domani.

Moltissime API storiche dei browser (o vecchie librerie) funzionano ancora con il vecchio sistema delle callback o degli eventi. Come facciamo a usarle con async/await se non restituiscono una Promise?

La risposta è la Promisification: l’atto di avvolgere una vecchia funzione basata su callback/eventi all’interno di una new Promise(), creando un “ponte” che la rende compatibile con il moderno async/await.

Vediamo i due esempi perfetti per capire questo meccanismo.

Esempio 1: Il Timer Perfetto (setTimeout)

La funzione nativa setTimeout è l’esempio scolastico di funzione basata su callback. Non puoi fare await setTimeout(1000). Dobbiamo promisificarla.

Creiamo una funzione attendi che fa da ponte:

javascript

// Creiamo il ponte
function attendi(ms) {
    return new Promise((resolve) => {
        // Quando il timer scatta, chiamiamo resolve() per sbloccare la Promise
        setTimeout(resolve, ms); 
    });
}

// Ora possiamo usarla nel mondo moderno!
async function avviaContoAllaRovescia() {
    console.log("3...");
    await attendi(1000);
    console.log("2...");
    await attendi(1000);
    console.log("1...");
    await attendi(1000);
    console.log("Via!");
}

Esempio 2: Il Caricamento Asincrono di un File (o Immagine)

Nel browser, il caricamento di risorse esterne come le immagini si basa sul modello a eventi (onload e onerror). Anche questo non supporta nativamente async/await.

Promisifichiamo il caricamento di un’immagine gestendo anche lo scenario di fallimento con reject:

javascript

// Creiamo il ponte per caricare un'immagine
function caricaImmagine(url) {
    return new Promise((resolve, reject) => {
        const img = new Image();
        img.src = url;

        // Se l'evento è di successo, risolviamo la Promise passando l'immagine
        img.onload = () => resolve(img);

        // Se l'immagine non esiste o c'è un errore di rete, rifiutiamo la Promise
        img.onerror = () => reject(new Error(`Impossibile caricare l'immagine da: ${url}`));
    });
}

// Ora la usiamo elegantemente all'interno di un blocco async
async function mostraGalleria() {
    try {
        console.log("Download dell'immagine in corso...");
        const fotoProfilo = await caricaImmagine("https://example.com");
        
        console.log(`Successo! Immagine caricata. Dimensioni: ${fotoProfilo.width}px`);
        document.body.appendChild(fotoProfilo);
    } catch (errore) {
        console.error("Errore catturato:", errore.message);
    }
}

Conclusione

La promisificazione è lo strumento definitivo per non rimanere bloccati nel passato. Ogni volta che ti trovi davanti a una vecchia API del browser guidata da eventi (come i vecchi flussi WebRTC, il tracciamento degli stati di connessione o il caricamento di file su nodi DOM), ricordati che ti basta costruire un ponte con una Promise esplicita.

Una volta fatto, potrai goderti la pulizia e la leggibilità di async/await, lasciando il Callback Hell solo come un brutto ricordo scolastico.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *