Tutorial > Come usare le Promise in Javascript

Come usare le Promise in Javascript

Pubblicato il: 07 settembre 2023

Javascript Promise Sviluppo

Introduzione

In Javascript. una Promise è un oggetto che rappresenta una operazione non svolta completamente nell'immediato ma che si compierà fino alla sua fine nel futuro.

Le promises vengono usate per le operazioni asincrone, ossia operazioni che lavorano separatamente dal thread principale ma che gli comunicano solamente quando il loro lavoro è terminato.

In questo tutorial ti mostreremo, con degli esempi di codice Javascript, come funzionano le Promises e come implementarle.

Pre-Requisiti

L'unico pre-requisito per comprendere e seguire questo tutorial sarebbe quello di avere conoscenza del linguaggio Javascript, per poter comprendere la sintassi e la semantica dei codici presenti nella guida.

Comprendere le Promises

Una Promise si spiega, per esempio, con un ordine di cui verificare la disponibilità. Un ordine, immaginandolo, dovrebbe essere definito in tre stati:

  • In sospeso
  • Accettato
  • Rifiutato

Nella casistica più comune, eseguito l'ordine esso sarà in sospeso e, successivamente alla verifica della disponibilità di un prodotto, ad esempio, potrà essere completato o rifiutato.

Adesso, partendo da tale esempio, ti mostreremo una possibile implementazione in Javascript per comprendere come lavorare con le Promises.

Creare Promises

Il codice servirà a creare un nuovo ordine, associandolo a una Promise. Nell'esempio, la Promise si svolgerà in due modi a seconda che il prodotto sia disponibile o meno(available), restituendo un feedback all'utente in entrambi i casi (resolve o reject).



var available = false;

// Promise
var makeOrder = new Promise(
    function (resolve, reject) {
        if (available) {
            var product = {
                brand: 'something',
                color: 'black'
            };
            resolve(product); 
        } else {
            var reason = new Error('prodotto non disponibile');
            reject(reason);
        }

    }
);

Per generalizzare, la sintassi della promise dovrebbe essere la seguente:

// sintassi
new Promise(function (resolve, reject) { ... } );

Svolgere le Promises

Completata la definizione di una promise, occorre richiamarla nell'esecuzione per svolgerla:

//

var makeOrder = ... // come nell'esempio precedente

// call our promise
var askforProduct = function () {
    makeOrder
        .then(function (fulfilled) {
            // order fulfilled
            console.log(fulfilled);
             // output: { brand: 'something', color: 'black' }
        })
        .catch(function (error) {
            // prodotto non disponibile,, ordine rifiutato
            console.log(error.message);
             // output: 'prodotto non disponibile'
        });
};

askforProduct();

Concatenare le Promises

Le promises sono concatenabili. A volte, per esempio, puoi generare le promises per rispondere a una promise precedente.


var showConfirmation = function(product) {
    return new Promise(
        function (resolve, reject) {
            var message = 'Order Confirmed' +
                product.color + ' ' + product.brand + ' product';

            resolve(message);
        }
    );
};

Abbreviato, il codice sarebbe:


var showConfirmation = function (product) {
    var message = 'Order Confirmed ' +
                product.color + ' ' + product.brand + ' product';

    return Promise.resolve(message);
};

Concatenando, showConfirmation parte necessariamente dopo makeOrder.



var askforProduct = function () {
        makeOrder
    .then(showConfirmation) 
    .then(function (fulfilled) {
            console.log(fulfilled);
         // output: 'Ordine confermato.'
        })
        .catch(function (error) {
            // oops, ordino non creato
            console.log(error.message);
         // output: 'ordine non creato'
        });
};

Questo è come le promesse vengono concatenate.

Le Promises sono asincrone

Le Promises sono asincrone. Proviamo a osservarne il comportamento in combinazione con un messaggio di log che venga espletato prima e dopo la chiamata di una promise.




var askforProduct = function () {
    console.log('messaggio prima della promise'); 
    makeOrder
        .then(showConfirmation)
        .then(function (fulfilled) {
            console.log(fulfilled);
        })
        .catch(function (error) {
            console.log(error.message);
        });
    console.log('Messaggio dopo la promise');
}    

La sequenza di questo output sarà, a scanso di come sarebbe interpretabile dal codice:

  1. Messaggio prima della promise
  2. Messaggio dopo la promise
  3. Output della Promise

Il codice si eseguirà senza bloccarsi o aspettare i risultati. Qualunque cosa debba aspettare l'esecuzione di una promise viene invece posta nelle parentesi di .then .

Casi d'uso delle Promises

Immagina, in un esempio di somma di valori, che uno di essi vada recuperato da una fonte esterna (server ,hdd, etc..).

Osserva, in primis, la differenza tra un codice che esegue una somma di valori normalmente o con un recupero da una fonte esterna:

  • Esecuzione normale:

    
        
    
    function add (n1, n2) {
        return n1 + n2;
    }
    
    const result = add(5, 7); // output immediato. Risultato = 12 
    
  • Recuperando i numeri dal server:

    
    
    const result = getAddResultFromServer('serverlink_withpassageofvalues');
    // se il server è offline viene restituito 'NaN'    
    

Nel secondo caso, se non ottienessi il risultato, il flusso delle esecuzione sarebbe bloccato al punto in cui non riesci a recuperare un valore che ti servirebbe per proseguire.Non potresti mai bloccare un intero processo di un programma solo per la sua dipendenza dalla disponibilità di una fonte esterna. Il discorso della asincronicità ti viene incontro.

Per implementare la asincronicità, al di fuori delle promises, potresti ricorrere alle meccaniche delle callback functions, ossia delle funzioni che necessitano di ricevere uno specifico input prima di eseguirsi. Questo, ti permette di proseguire il flusso di esecuzione e di invocare una determinata funzione, dipendente da una fonte esterna, solo in caso in cui le condizioni di chiamata di quella funzione si verifichino.

Un esempio di definizione e uso di una callback function:


 
function addAsync (n1, n2, callback) {
    // use jQuery getJSON callback API
    return $.getJSON('serverlink', {
        n1: n1,
        n2: n2
    }, callback);
}

addAsync(5, 7, success => {
    // callback
    const result = success; // Result = 12
});

Nel caso di un programma così semplice, prediligere le callback functions piuttosto che le promises non sortisce una grande differenza. Ma che succederebbe se dovessi implementare una somma di elementi superiori a 2?

Guarda, nell'esempio, come verrebbe implementata la somma di più numeri normalmente e con le callbacks:

  • Caso 1. Ripeti più volte la funzione add prendendo in input il risultato precedente più il nuovo numero:

    
    
    
    let result1, result2, result3;
    
     function add (n1, n2) {
        return n1 + n2;
    }
    
    result1 = add(5, 7); // risultato1 = 12
    result2 = add(result1, 3); // risultato2 = 15
    result3 = add(result2, 4); // risultato3 = 19
    
    console.log('la somma dei risultati è ' + result3);
    console.log(result1, result2, result3);
            
  • Caso 2. Ripeti più volte la callback function innestandone una dentro l'altra:

    
    
    
    let result1, result2, result3;
    
    function addAsync (n1, n2, callback) {
        // jQuery getJSON callback API
        // https://api.jquery.com/jQuery.getJSON/
        return $.getJSON('serverlink', {
            n1: n1,
            n2: n2
        }, callback);
    }
    
    addAsync(5, 7, success => {
        // callback 1
        result1 = success; // result1 = 12
    
        addAsync(result1, 3, success => {
            // callback 2
            result2 = success; // result2 = 15
    
            addAsync(result2, 4, success => {
                // callback 3
                result3 = success; // risultato = 19
    
                console.log('la somma dei risultati è ' + result3);
                console.log(result1, result2, result3);
            });
        });
    });    
        

Come puoi osservare, il metodo dell'innesto di funzioni, conosciuto nella programmazione, risulta essere un po' meno comprensibile e difficile da implementare per un utente rispetto ad altre modalità.

Per limitare la complessità, ti mostriamo come sarebbe lo stesso codice, utilizzando le promises:


    

let result1, result2, result3;

function addAsync(num1, num2) {
    // usa la ES6 fetch API, che restituisce una promise
    return fetch(`http://www.example.com?num1=${num1}&num2=${num2}`)
        .then(x => x.json()); 
}

addAsync(5, 7)
    .then(success => {
        result1 = success;
        return result1;
    })
    .then(success => addAsync(success, 3))
    .then(success => {
        result2 = success;
        return result2;
    })
    .then(success => addAsync(success, 4))
    .then(success => {
        result3 = success;
        return result3;
    })
    .then(success => {
        console.log('La somma dei risultati è ' + success) //totale = 19
        console.log(result1, result2, result3)
    });

Invece che l'innesto di callback functions, si è optato per implementare l'attesa dei risultati in input all'interno della sintassi .then, spiegata precedentemente.

Come puoi osservare, con le promises si raggiunge lo stesso risultato ma si migliora di gran lunga la leggibilità e la comprensibilità del codice.

Conclusioni

A seguito di questo tutorial, sarai in grado di capire la natura delle promises, percepirne le modalità di impiego e perché sia conveniente farne ricorso.

Ricorda che, nell'ambito dello sviluppo, ci sono molti approcci e modi differenti di giungere alla stessa soluzione. Tuttavia, come hai potuto osservare durante gli esempi, ci sono metodi che risultano essere più efficaci (stesso risultato, meno impiego di risorse) ed è sempre buona norma approfondire quali vie siano da preferire, nell'ottica del perseguire questo fattore qualitativo di un codice.