Promise JavaScript
Impara le Promise JavaScript: stati pending, fulfilled e rejected, la funzione executor con resolve e reject, then/catch/finally e la coda delle microtask, con esempi eseguibili.
Le Promise in JavaScript sono uno strumento potente per gestire le operazioni asincrone, che consente agli sviluppatori di scrivere codice più pulito e robusto. Questo capitolo spiega cos'è una promise, i tre stati in cui può trovarsi, come funziona la funzione executor con resolve e reject, come consumare i risultati con .then, .catch e .finally, e quando i callback delle promise vengono effettivamente eseguiti (la coda delle microtask). Comprendere questi fondamentali è essenziale prima di passare al chaining, alla Promise API e all'async/await.
Introduzione alle Promise JavaScript
Una Promise è un object che rappresenta un valore che potrebbe non essere ancora disponibile, ma lo sarà in un momento futuro. Invece di passare un callback a una funzione asincrona sperando che venga chiamato, si riceve immediatamente un object promise al quale si allegano i callback. Questo evita i callback profondamente annidati, spesso chiamati "callback hell", e fornisce un modo unico e coerente per gestire sia il successo che il fallimento.
Un tipico compito asincrono — una richiesta di rete, un timer, la lettura di un file — non ha il suo risultato pronto immediatamente. La promise è un segnaposto per quel risultato.
I Tre Stati di una Promise
Una promise si trova sempre in esattamente uno dei tre stati:
- pending — lo stato iniziale; l'operazione non è ancora completata.
- fulfilled — l'operazione è completata con successo e la promise ha un valore.
- rejected — l'operazione è fallita e la promise ha un motivo (solitamente un
Error).
Una promise pending può passare allo stato fulfilled o rejected. Una volta fatto, è settled e non può mai cambiare stato di nuovo. Questa transizione unidirezionale e unica è ciò che rende le promise prevedibili: un callback .then collegato a una promise già fulfilled verrà comunque eseguito, e una promise non può mai passare da fulfilled a rejected.
┌─────────────┐ resolve(value) ┌─────────────┐
│ pending │ ────────────────▶ │ fulfilled │
new Promise ─▶ │ │ └─────────────┘
│ │ reject(reason) ┌─────────────┐
└─────────────┘ ────────────────▶ │ rejected │
└─────────────┘Creare una Promise
Per creare una promise, si chiama il costruttore Promise passandogli una funzione chiamata executor. L'executor viene eseguito immediatamente e in modo sincrono nel momento in cui la promise viene creata. Riceve due funzioni come argomenti, convenzionalmente chiamate resolve e reject:
- Chiama
resolve(value)per portare la promise allo stato fulfilled convalue. - Chiama
reject(reason)per rigettare la promise conreason.
Finché non si chiama una di esse, la promise rimane pending.
L'executor riceve resolve e reject in modo da poter risolvere la promise una volta terminato il lavoro asincrono. Qui la promise diventa fulfilled dopo un timer di un secondo:
Nota: Solo la prima chiamata a
resolveorejectè rilevante. Una volta che una promise è settled, qualsiasi ulteriore chiamata aresolveorejectviene ignorata. Inoltre, se l'executor genera un errore in modo sincrono, la promise viene automaticamente rigettata con quell'errore.
Gestire i Risultati con .then, .catch e .finally
Una volta creata una promise, se ne consuma il risultato con i metodi .then, .catch e .finally. Questi sono il modo in cui il resto del codice reagisce a una promise settled.
Il metodo then
Il metodo .then viene usato per programmare un callback da eseguire quando la promise è fulfilled. Affinché una promise sia fulfilled, deve essere chiamato il metodo resolve. L'argomento passato al metodo resolve sarà il valore finale della promise.
In questo codice, la promise sarà fulfilled solo dopo che il timeout di 1000 ms è scaduto e il metodo resolve viene chiamato con "Done!".
La funzione nella parte then viene eseguita solo dopo che il metodo resolve viene chiamato.
.then può anche accettare un secondo argomento — un gestore di rifiuto — ma usare un .catch separato (come descritto di seguito) è più chiaro e cattura anche gli errori dai gestori precedenti.
Il metodo .catch
Il metodo .catch viene usato per gestire la promise se viene rigettata. Ciò significa che nel blocco della funzione promise viene generato un errore oppure viene chiamato il metodo reject.
Per pattern di gestione degli errori più avanzati — rifiuto vs. errori generati, rilancio e recupero all'interno di una catena — vedere Gestione degli Errori con le Promise.
Il metodo .finally
Il metodo .finally consente di eseguire codice dopo che la promise è settled, indipendentemente dal suo esito. Non riceve argomenti (non sa se la promise è stata fulfilled o rejected) e passa il risultato o l'errore attraverso senza modifiche, rendendolo ideale per operazioni di pulizia come nascondere un indicatore di caricamento.
Quando Vengono Eseguiti i Callback delle Promise? (Microtask)
Una sorpresa comune è che i callback .then, .catch e .finally non vengono mai eseguiti in modo sincrono, anche se la promise è già settled. Vengono schedulati nella coda delle microtask, che il motore elabora solo dopo che il codice sincrono corrente è terminato.
Ciò significa che il codice sincrono viene sempre eseguito per primo, e i callback delle promise vengono eseguiti prima dei timer (setTimeout), che si trovano nella coda separata delle macrotask.
Anche se la promise viene risolta immediatamente e il timeout è 0, il callback della promise (3) viene eseguito prima del callback del timeout (4), perché la coda delle microtask viene completamente svuotata prima che venga eseguita la macrotask successiva.
Recuperare Dati da un API usando le Promise
Questo esempio mostra come recuperare dati da un API remota usando le promise.
Concatenare le Promise
Il chaining delle promise è una funzionalità potente che consente di collegare più operazioni asincrone. Ogni .then restituisce una nuova promise, e qualsiasi valore che si return da un gestore diventa il valore di fulfillment di quella nuova promise — ecco perché l'esempio seguente porta un valore attraverso diversi passaggi. Per maggiori informazioni, consultare JavaScript: Promise e Chaining.
Integrare async/await con le Promise JavaScript
Usare async/await in modo efficace può semplificare la gestione delle operazioni asincrone, rendendo il codice più pulito e facile da comprendere, mantenendo al contempo tutta la potenza delle promise JavaScript. Imparerai di più a riguardo in JavaScript async/await, ma ecco un semplice esempio.
Conclusione
Padroneggiare le promise JavaScript è fondamentale per qualsiasi sviluppatore che voglia gestire le operazioni asincrone in modo efficiente. Ricorda il modello di base: una promise è pending finché l'executor non chiama resolve o reject, dopodiché è settled per sempre; si legge il risultato con .then/.catch/.finally; e quei callback vengono sempre eseguiti come microtask, dopo il codice sincrono corrente.
Dove andare dopo
- JavaScript: Promise e Chaining — sequenziare passaggi asincroni e trasferire valori.
- La Promise API — helper statici come
Promise.all,Promise.race,Promise.allSettledePromise.resolve. - Gestione degli Errori con le Promise — rifiuti, errori generati e pattern di recupero.
- JavaScript async/await — scrivere codice basato su promise che si legge come codice sincrono.