W3docs

Promisification in JavaScript

Impara la promisification in JavaScript: racchiudi callback error-first in una Promise, crea un helper promisify() generico e conosci i suoi limiti.

Cos'è la Promisification?

La Promisification è l'atto di racchiudere una funzione basata su callback in modo che restituisca una Promise invece di accettare una callback. La esegui una volta sola, dopodiché puoi usare .then(), .catch(), il chaining e async/await su una funzione che non era mai stata progettata per essi.

Questa pagina spiega come racchiudere una singola API con callback error-first, come costruire un helper generico riutilizzabile promisify(), come gestire callback che restituiscono più di un risultato e i casi in cui la promisification non funziona.

Perché usare la Promisification?

Le API JavaScript più vecchie e la maggior parte della libreria standard di Node.js comunicano i propri risultati tramite una callback passata come argomento. Questo stile si annida rapidamente e disperde la gestione degli errori:

getUser(id, (err, user) => {
  if (err) return handleError(err);
  getOrders(user, (err, orders) => {
    if (err) return handleError(err);
    getTotal(orders, (err, total) => {
      if (err) return handleError(err);
      console.log(total);
    });
  });
});

Se le stesse funzioni restituissero promise, la logica si appiattisce in una singola catena lineare (o poche righe con await) con un unico .catch() per l'intero flusso. La promisification è il ponte tra questi due mondi — consulta Callbacks e oltre per il lato callback della storia.

La Convenzione Error-First per le Callback

Prima di racchiudere qualsiasi cosa, devi conoscere la struttura di ciò che stai racchiudendo. Le callback di Node.js seguono la convenzione error-first (o "Node-style"): la callback è l'ultimo argomento e viene chiamata come callback(error, result).

  • In caso di errore, error è un oggetto Error e result è undefined.
  • In caso di successo, error è null e result contiene il valore.

La promisification mappa questo direttamente: un error non null diventa reject(error), mentre un result riuscito diventa resolve(result).

Racchiudere una Singola API con Callback

Ecco il pattern fondamentale. Racchiudiamo una funzione in stile callback in una nuova Promise, chiamando reject per l'errore e resolve per il valore. L'esempio simula un'API error-first con setTimeout in modo che funzioni ovunque, incluso nel browser:


javascript— editable

Il wrapper accetta lo stesso argomento id, lo inoltra e fornisce la propria callback che fa da ponte tra resolve/reject. Chi chiama la funzione non vedrà mai più una callback.

Usare il Wrapper con async/await

Il vero vantaggio di una funzione che restituisce una promise è che funziona con async/await, trasformando il codice asincrono in qualcosa che si legge dall'alto verso il basso:


javascript— editable

Un Helper Generico promisify()

Scrivere un wrapper a mano per ogni funzione diventa ripetitivo. Un helper generico prende qualsiasi funzione error-first e ne restituisce una versione che restituisce una promise. Il trucco consiste nel raccogliere tutti gli argomenti originali con un rest parameter e poi aggiungere la propria callback:


javascript— editable

Poiché l'helper usa ...args e inoltra this, funziona con funzioni che hanno qualsiasi numero di argomenti iniziali. In Node.js, la libreria standard include esattamente questo come util.promisify, quindi raramente è necessario scriverne uno proprio:

const fs = require('fs');
const util = require('util');

const readFile = util.promisify(fs.readFile);

readFile('file.txt', 'utf8')
  .then(data => console.log(data))
  .catch(err => console.error(err));

Gestire Callback con Più Argomenti

Il semplice helper presuppone che la callback fornisca un singolo risultato: callback(err, result). Alcune API passano più valori, come callback(err, header, body). Un semplice resolve(result) ignorerebbe silenziosamente tutto ciò che va oltre il primo valore.

Una promise può essere risolta con un solo valore, quindi occorre raccogliere gli argomenti extra in un array (o in un object) e risolvere con quello:


javascript— editable

util.promisify di Node supporta la stessa idea tramite un simbolo personalizzato (util.promisify.custom), ma per funzioni ad hoc un array è l'approccio più semplice.

Limitazioni e Avvertenze

La promisification è meccanica, ma ha limiti reali:

  • Si aspetta la convenzione error-first. Se una funzione segnala gli errori in altro modo — ad esempio con un valore boolean di ritorno, un'eccezione lanciata o l'ordine (result, err) — un helper generico la interpreterà erroneamente. In tal caso, crea il wrapper a mano.
  • Gestisce solo un singolo completamento. Le promise si stabilizzano una volta sola. Una funzione che invoca la propria callback più volte (eventi, stream, setInterval, una callback di avanzamento) non può essere promisificata — solo la prima chiamata risolverebbe la promise; le successive vengono ignorate. Usa un'API event o un async iterator per valori ripetuti.
  • Non puoi annullare una promise. Se l'API callback sottostante supporta la cancellazione (come svuotare un timer), questa capacità viene persa una volta nascosta dietro una promise.
  • Il wrapper cambia la firma della chiamata. Chi chiama la funzione deve ora usare .then/await invece di passare una callback. Non promisificare una funzione che del codice esistente chiama ancora in stile callback senza mantenere entrambe le versioni.
  • Un throw all'interno dell'executor viene comunque rigettato. Il codice che viene eseguito in modo sincrono all'interno di new Promise((resolve, reject) => { ... }) viene intercettato e trasformato in un rigetto — ma un errore lanciato successivamente all'interno di una callback asincrona non viene intercettato automaticamente, ed è esattamente per questo che devi chiamare reject(err) esplicitamente.

Best Practice

  • Promisifica al confine. Converti le API di I/O e timer una volta sola, vicino al punto in cui entrano nel tuo codice, e mantieni il resto del codebase basato su promise.
  • Preferisci i built-in. In Node.js, usa util.promisify (o i moduli fs/promises, dns/promises, ecc.) prima di scrivere un wrapper a mano.
  • Gestisci sempre il rigetto. Aggiungi un .catch() o racchiudi await in un try/catch; un rigetto non gestito può causare il crash di un processo Node.
  • Mantieni i nomi prevedibili. Una convenzione comune è aggiungere il suffisso Async alla versione basata su promise (readFileAsync) in modo che entrambi gli stili possano coesistere.

Argomenti Correlati

  • JavaScript Promise — l'object che stai creando quando promisifichi.
  • Promises Chaining — concatena le chiamate promisificate in modo pulito.
  • Async/Await — la sintassi che fa sì che le funzioni promisificate si leggano come codice sincrono.
  • Callbacks e oltre — il pattern da cui stai convertendo.
  • Promise API — combina più chiamate promisificate con Promise.all e simili.

Esercizio

Pratica
Qual è la funzione principale della promisification in JavaScript?
Qual è la funzione principale della promisification in JavaScript?
Was this page helpful?