Menu

Promise in TypeScript: Promise<T>, Promise.all e allSettled

Come si tipizzano le promise in TypeScript: il tipo Promise<T>, tipizzare new Promise e resolve, come then cambia il tipo, perché catch ti dà any, Promise.all con risultati in tupla, i tipi dei risultati di Promise.allSettled e come avvolgere API a callback in una promise tipizzata.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

In TypeScript una promise ha il tipo Promise<T>, dove T è il tipo del valore con cui si risolve. Una funzione che restituisce una promise lo dichiara nel suo tipo di ritorno, e il valore che ottieni da then o await ha tipo T.

I meccanismi delle promise (stati, coda dei microtask, concatenazione) sono JavaScript puro, trattati in Promise in JavaScript. Questa pagina riguarda i tipi.

Tipizzare new Promise e resolve

Quando new Promise è il valore di ritorno di una funzione con un tipo di ritorno dichiarato, T viene da lì. Da solo, TypeScript non inferisce T dalle chiamate a resolve, e ottieni Promise<unknown>. Passa l'argomento di tipo in modo esplicito:

Due dettagli. resolve è tipizzato a partire da T, quindi resolve("42") in una Promise<number> è un errore di compilazione. E resolve() senza argomenti è permesso solo quando T include void: in una Promise<number> è l'errore TS2794 (Expected 1 arguments, but got 0. Did you forget to include 'void' in your type argument to 'Promise'?).

Il lato reject non è tipizzato. Non esiste Promise<T, E>: una promise può essere rifiutata con qualsiasi valore.

then, catch e finally

Ogni then restituisce una nuova promise tipizzata in base a ciò che restituisce la sua callback. Se la callback restituisce una promise, TypeScript la scarta, quindi non ottieni mai Promise<Promise<T>>.

Il parametro della callback di catch ha tipo any nella libreria standard, non unknown. Niente ti impedisce di scrivere e.message su un valore che potrebbe essere una stringa. Annotalo come unknown, come sopra, e restringilo prima di usarlo. Un try/catch attorno ad await fa di meglio: con strict, la sua variabile catch è già unknown (vedi gestione degli errori).

Promise.all restituisce una tupla

Dato un array letterale di promise di tipi diversi, Promise.all restituisce una promise di una tupla, in cui ogni posizione mantiene il proprio tipo:

Si possono mescolare anche valori che non sono promise, e passano invariati. Promise.all viene rifiutata appena un input viene rifiutato, e gli altri risultati vanno persi. Promise.race si risolve o viene rifiutata con il primo che si conclude ed è tipizzata come l'unione degli input; Promise.any si risolve con il primo successo e viene rifiutata con un AggregateError solo se tutti gli input falliscono.

Promise.allSettled e il suo tipo di risultato

Promise.allSettled aspetta ogni input e non viene mai rifiutata. Ogni risultato è un PromiseSettledResult<T>, un'unione che restringi con il campo status:

r.value esiste solo dopo il controllo su status, perché la variante rifiutata non ha value. reason è any, per lo stesso motivo di catch. Il filter usa un type predicate così che l'array filtrato sia tipizzato come risultati riusciti.

Avvolgere un'API a callback in una promise

Le API più vecchie comunicano i risultati tramite una callback, spesso nello stile di Node (err, result) => void. Avvolgile una volta in una funzione che restituisce una promise tipizzata, e il resto del codice può usare await:

In Node, util.promisify lo fa per le funzioni che seguono la convenzione (err, result), e molti moduli integrati hanno già versioni con promise (node:fs/promises, node:timers/promises).

Errori comuni

  • Restituire T dove la firma dice Promise<T>, o il contrario. Una funzione non async dichiarata Promise<User> deve restituire una promise; una funzione async ne restituisce una automaticamente.
  • Dimenticare di gestire una promise. Una chiamata come save(user); senza await, then o catch compila senza problemi, e un rifiuto diventa un rifiuto non gestito (che per impostazione predefinita termina un processo Node). La regola di typescript-eslint no-floating-promises intercetta questi casi.
  • Fidarsi del tipo di .catch((e) => ...). Il suo e è any. Annotalo come unknown.
  • Usare new Promise attorno a qualcosa che restituisce già una promise. Restituisci o fai await della promise esistente.

Domande frequenti

Cos'è Promise<T> in TypeScript?

Promise<T> è il tipo di una promise che si risolve con un valore di tipo T. Una funzione che restituisce Promise<string> consegna una promise la cui callback di then, o il cui await, dà una string. Una promise che si risolve senza valore è Promise<void>.

Come tipizzo new Promise in TypeScript?

Passa l'argomento di tipo: new Promise<number>((resolve, reject) => ...). Senza di esso, TypeScript non riesce a inferire il valore dalle chiamate a resolve e il risultato è Promise<unknown>. Per una promise che si risolve senza nulla, usa new Promise<void>(resolve => ...) così che resolve() senza argomenti sia permesso.

Di che tipo è l'errore nel catch di una promise?

any. Il parametro reason della callback di catch ha tipo any nella libreria standard, perché si può lanciare o rifiutare qualsiasi cosa. Annotalo tu come unknown (.catch((e: unknown) => ...)) e restringilo con instanceof Error prima di usarlo.

Come funziona Promise.all con i tipi in TypeScript?

Promise.all su un array letterale restituisce un tipo tupla con un elemento per ogni input, in ordine: Promise.all([getUser(), getCount()]) è Promise<[User, number]>, quindi con la destrutturazione ogni valore ha il suo tipo. Su un array di un solo tipo, T[], restituisce Promise<T[]>.

Che differenza c'è tra Promise.all e Promise.allSettled?

Promise.all viene rifiutata appena un input viene rifiutato. Promise.allSettled si risolve sempre, con un array di oggetti PromiseSettledResult<T>: { status: "fulfilled", value } o { status: "rejected", reason }. Controlla status per restringere ciascuno.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA