W3docs

La promisification en JavaScript

Apprenez la promisification JavaScript : encapsulez des callbacks dans une Promise, créez un helper promisify() générique et découvrez ses limites.

Qu'est-ce que la promisification ?

La promisification consiste à encapsuler une fonction basée sur des callbacks afin qu'elle retourne une Promise plutôt que de prendre un callback en paramètre. On effectue cette opération une seule fois, puis on peut utiliser .then(), .catch(), le chaînage et async/await sur une fonction qui n'était pas conçue pour cela.

Cette page explique comment encapsuler une API à callback de type error-first, comment créer un helper générique promisify() réutilisable, comment gérer des callbacks qui retournent plusieurs résultats, et les cas où la promisification ne fonctionne pas.

Pourquoi promisifier ?

Les APIs JavaScript plus anciennes et la majeure partie de la bibliothèque standard Node.js communiquent leurs résultats via un callback passé en paramètre. Ce style s'imbrique rapidement et disperse la gestion des erreurs :

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);
    });
  });
});

Si ces mêmes fonctions retournaient des promises, la logique s'aplatirait en une seule chaîne linéaire (ou quelques lignes await) avec un seul .catch() pour l'ensemble du flux. La promisification fait le pont entre ces deux mondes — consultez Les callbacks et au-delà pour la partie callback.

La convention du callback error-first

Avant d'encapsuler quoi que ce soit, vous devez connaître la forme que vous encapsulez. Les callbacks Node.js suivent la convention error-first (ou « style Node ») : le callback est le dernier argument et est appelé sous la forme callback(error, result).

  • En cas d'échec, error est un objet Error et result est undefined.
  • En cas de succès, error est null et result contient la valeur.

La promisification établit cette correspondance directement : une error non null devient reject(error), et un result réussi devient resolve(result).

Encapsuler une API à callback unique

Voici le schéma de base. On encapsule une fonction à style callback dans une nouvelle Promise, en appelant reject pour l'erreur et resolve pour la valeur. L'exemple simule une API error-first avec setTimeout afin de fonctionner partout, y compris dans le navigateur :


javascript— editable

L'encapsuleur accepte le même argument id, le transmet et fournit son propre callback qui fait le pont vers resolve/reject. L'appelant ne voit plus jamais de callback.

Utiliser l'encapsuleur avec async/await

Le vrai bénéfice d'une fonction retournant une promise est qu'elle fonctionne avec async/await, transformant le code asynchrone en quelque chose qui se lit de haut en bas :


javascript— editable

Un helper générique promisify()

Écrire un encapsuleur à la main pour chaque fonction devient répétitif. Un helper générique prend n'importe quelle fonction error-first et retourne une version qui retourne une promise. L'astuce consiste à collecter tous les arguments originaux avec un paramètre rest, puis à ajouter notre propre callback :


javascript— editable

Parce que le helper utilise ...args et transmet this, il fonctionne pour des fonctions avec n'importe quel nombre d'arguments initiaux. Dans Node.js, la bibliothèque standard fournit exactement cela sous la forme de util.promisify, vous n'avez donc rarement besoin d'écrire le vôtre :

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));

Gérer les callbacks avec plusieurs arguments

Le helper simple suppose que le callback fournit un seul résultat : callback(err, result). Certaines APIs transmettent plusieurs valeurs, comme callback(err, header, body). Un simple resolve(result) ignorerait silencieusement tout ce qui suit la première valeur.

Une promise ne peut se résoudre qu'avec une seule valeur, il faut donc collecter les arguments supplémentaires dans un array (ou un object) et résoudre avec celui-ci :


javascript— editable

util.promisify de Node prend en charge la même idée via un symbole personnalisé (util.promisify.custom), mais pour des fonctions ponctuelles, un array est l'approche la plus simple.

Limitations et pièges

La promisification est mécanique, mais elle a des limites réelles :

  • Elle suppose la convention error-first. Si une fonction signale les erreurs d'une autre manière — par exemple un retour boolean, une exception levée, ou un ordre (result, err) — un helper générique l'interprétera mal. Encapsulez-les à la main.
  • Elle ne gère qu'une seule complétion. Les promises se résolvent une seule fois. Une fonction qui invoque son callback plusieurs fois (événements, flux, setInterval, un callback de progression) ne peut pas être promisifiée — seul le premier appel résoudrait la promise ; les appels suivants sont ignorés. Utilisez une API d'événements ou un itérateur asynchrone pour les valeurs répétées.
  • Vous ne pouvez pas annuler une promise. Si l'API à callback sous-jacente prend en charge l'annulation (comme l'effacement d'un timer), cette capacité est perdue une fois cachée derrière une promise.
  • L'encapsuleur change la signature d'appel. Les appelants doivent désormais utiliser .then/await au lieu de passer un callback. Ne promisifiez pas une fonction que certain code appelle encore en style callback sans conserver les deux versions.
  • Un throw à l'intérieur de l'exécuteur provoque quand même un rejet. Le code qui s'exécute de manière synchrone à l'intérieur de new Promise((resolve, reject) => { ... }) est intercepté et transformé en rejet — mais une erreur levée ultérieurement à l'intérieur d'un callback asynchrone n'est pas interceptée automatiquement, ce qui explique exactement pourquoi vous devez appeler reject(err) explicitement.

Bonnes pratiques

  • Promisifiez à la frontière. Convertissez les APIs d'I/O et de timer une seule fois, là où elles entrent dans votre code, et gardez le reste de votre base de code basée sur les promises.
  • Préférez les fonctions intégrées. Dans Node.js, utilisez util.promisify (ou les modules fs/promises, dns/promises, etc.) avant d'écrire votre propre encapsuleur.
  • Gérez toujours le rejet. Attachez un .catch() ou encapsulez await dans un try/catch ; un rejet non géré peut faire planter un processus Node.
  • Gardez des noms prévisibles. Une convention courante est de suffixer la version promise avec Async (readFileAsync) afin que les deux styles puissent coexister.

Sujets connexes

Pratique

Pratique
Quelle est la fonction principale de la promisification en JavaScript ?
Quelle est la fonction principale de la promisification en JavaScript ?
Was this page helpful?