22 novembre 2020

Promisification

Promisification – è una parola lunga per una trasformazione semplice. È la conversione di una funzione che accetta una callback in una funzione che ritorna una promise.

Per essere più precisi, creiamo una funzione wrapper che fa lo stesso, chiamando internamente quella originale, ma ritornando una promise.

Queste trasformazioni sono spesso necessarie nella vita reale, dato che molte funzioni e librerie sono basate su callback. Ma le promise sono più pratiche. Per questo motivo ha senso trasformarle in promise.

Per esempio, abbiamo loadScript(src, callback) dal capitolo Introduzione: callbacks.

function loadScript(src, callback) {
  let script = document.createElement('script');
  script.src = src;

  script.onload = () => callback(null, script);
  script.onerror = () => callback(new Error(`Errore caricamento script per ${src}`));

  document.head.append(script);
}

// utilizzo:
// loadScript('path/script.js', (err, script) => {...})

Trasformiamolo in una promise. La nuova funzione loadScriptPromise(src) farà lo stesso, ma accetta solo src (senza callback) e ritorna una promise.

Here it is:

let loadScriptPromise = function(src) {
  return new Promise((resolve, reject) => {
    loadScript(src, (err, script) => {
      if (err) reject(err);
      else resolve(script);
    });
  });
};

// uso:
// loadScriptPromise('path/script.js').then(...)

Ora loadScriptPromise si adatta bene al nostro codice basato sulle promise.

Come possiamo vedere, delega tutto il lavoro alla loadScript originale, passando la sua callback che si traduce nel resolve/reject della promise.

Dato che abbiamo bisogno di trasformare in (promisify) molte funzione, ha senso usare un helper.

Questo è molto semplice – promisify(f) sotto prende una funzione da trasformare in promise f e ritorna una funzione wrapper.

Quel wrapper fa la stessa cosa del codice sopra: ritorna una promise e passa la chiamata alla f originale, tracciando il risultato in una sua callback:

function promisify(f) {
  return function (...args) { // ritorna una funzione wrapper
    return new Promise((resolve, reject) => {
      function callback(err, result) { // la nostra callback f
        if (err) {
          reject(err);
        } else {
          resolve(result);
        }
      }

      args.push(callback); // aggiunge la nostra callback custom alla fine degli argomenti

      f.call(this, ...args); // chiama la funzione originale
    });
  };
}

// uso:
let loadScriptPromise = promisify(loadScript);
loadScriptPromise(...).then(...);

Qui diamo per scontato che la funzione originale aspetti una callback con due argomenti (err, result). Questo è quello che troveremo più spesso. Poi la nostra callback custom è esattamente nel formato corretto, e promisify funziona perfettamente per questo caso.

Ma cosa succederebbe se f aspettasse una callback con più argomenti callback(err, res1, res2)?

Ecco una modifica di promisify che ritorna un array di diversi risultati della callback:

// promisify(f, true) per avere un array di risultati
function promisify(f, manyArgs = false) {
  return function (...args) {
    return new Promise((resolve, reject) => {
      function callback(err, ...results) { // la nostra callback custom per f
        if (err) {
          reject(err);
        } else {
          // risolve con tutti i risultati della callback se manyArgs è specificato
          resolve(manyArgs ? results : results[0]);
        }
      }

      args.push(callback);

      f.call(this, ...args);
    });
  };
}

// usage:
f = promisify(f, true);
f(...).then(arrayOfResults => ..., err => ...);

In alcuni casi, err può essere del tutto assente: callback(result), o c’è qualcosa di particolare nel formato della callback, allora possiamo trasformare in promise (promisify) queste funzioni senza usare un helper, manualmente.

Ci sono anche moduli con delle funzioni per trasformare in promise un po’ più flessibili, ad esempio es6-promisify. In Node.js è presente una funzione util.promisify.

Da notare:

La trasformazione in promise (promisification) è un ottimo approccio, specialmente quando si utilizza async/await (nel prossimo capitolo), ma non è un sostituto totale per le callback.

Ricorda, una promise può avere un solo risultato, ma una callback può tecnicamente essere chiamata più volte.

Così la trasformazione in (promisification) è intesa solo per le funzioni che chiameranno la callback una volta sola. Le chiamate successive saranno ignorate.

Mappa del tutorial

Commenti

leggi questo prima di lasciare un commento…
  • Per qualsiasi suggerimento - per favore, apri una issue su GitHub o una pull request, piuttosto di lasciare un commento.
  • Se non riesci a comprendere quanto scitto nell'articolo – ti preghiamo di fornire una spiegazione chiara.
  • Per inserire delle righe di codice utilizza il tag <code>, per molte righe – includile nel tag <pre>, per più di 10 righe – utilizza una sandbox (plnkr, jsbin, codepen…)