new Promise en JavaScript : fabriquer une promesse à la main

Le constructeur new Promise fabrique une promesse à partir de resolve et reject : emballer une API à callback, et les deux pièges qui rendent le code muet.
4 min de lecture
Believemy logo

La plupart du temps, personne n'écrit new Promise : une fonction async en renvoie déjà une, et les interfaces modernes en produisent d'elles-mêmes.

Reste un cas où il devient indispensable : quand le travail à attendre n'annonce sa fin que par un callback.

new Promise : emballer un callbackL'exécuteur passé à new Promise reçoit resolve et reject, et part immédiatement. Il appelle l'interface à callback, qui rend une erreur en premier argument et la valeur en second. Si l'erreur n'est pas nulle, elle part dans reject et ressort dans catch ; sinon la valeur part dans resolve et ressort dans then. Le premier des deux appels règle la promesse, les suivants sont ignorés.Emballer un callback dans une promessenew Promise((resolve, reject) => …)l'exécuteur part immédiatementcallback(err, valeur)err ≠ nullreject(err)→ .catch()err = nullresolve(valeur)→ .then()le premier appel gagne, les suivants sont ignoréssans resolve ni reject, elle attend indéfiniment


Définition

new Promise prend une fonction, appelée exécuteur, qui reçoit deux fonctions de contrôle : resolve pour livrer une valeur, reject pour signaler un échec. L'exécuteur part immédiatement, dès la création de l'objet.

JAVASCRIPT
const attendre = (ms) => new Promise((resolve) => {
  setTimeout(() => resolve("fini"), ms);
});

attendre(200).then((v) => console.log(v));   // fini après 200 ms

La promesse reste en attente tant que ni resolve ni reject n'a été appelé. Une Promise qui n'appelle jamais l'un des deux n'échoue pas : elle ne se règle jamais, et le await placé devant attend indéfiniment.


Emballer une interface à callback

C'est l'usage principal du constructeur. Une fonction à la convention erreur en premier se convertit sans effort : l'erreur va dans reject, le résultat dans resolve.

JAVASCRIPT
function lireConfig(chemin, callback) {
  if (!chemin) return callback(new Error("Chemin manquant"));
  callback(null, { theme: "sombre" });
}

const lireConfigPromise = (chemin) => new Promise((resolve, reject) => {
  lireConfig(chemin, (err, valeur) => {
    if (err) reject(err);
    else resolve(valeur);
  });
});

lireConfigPromise("app.json").then((c) => console.log(c.theme));   // sombre

Une fois cette enveloppe posée, l'appel rejoint le reste du code : then(), catch, await et Promise.all() fonctionnent dessus comme sur n'importe quelle promesse.


Les deux pièges du constructeur

  • Le premier appel gagne. Un resolve suivi d'un reject ne provoque aucune erreur : les appels suivants sont ignorés en silence. Un return devant chaque appel évite d'y revenir.
  • L'exécuteur n'est protégé qu'en surface. Un throw écrit directement dans l'exécuteur devient bien un rejet. Mais un throw depuis un callback asynchrone, dans un setTimeout, part hors de la promesse et personne ne l'attrape.
JAVASCRIPT
// Devient un rejet : le throw est dans l'exécuteur
new Promise(() => { throw new Error("visible"); })
  .catch((err) => console.log("attrapé :", err.message));

// Ne devient rien : le throw part plus tard, hors de l'exécuteur
// new Promise(() => setTimeout(() => { throw new Error("perdu"); }, 0));
Bon à savoir

Promise.resolve(valeur) et Promise.reject(erreur) fabriquent en une expression une promesse déjà réglée. Pratique pour un cache : la fonction rend une valeur déjà connue ou un appel réseau, sans que l'appelant ait à les distinguer.


Questions fréquentes

Question

Peut-on remplacer le constructeur par une fonction async ?

Presque toujours, oui. Si le travail à attendre rend déjà une promesse, l'envelopper dans new Promise ne fait qu'ajouter une couche et une occasion d'oublier un rejet. Le constructeur ne se justifie que face à un callback, un événement ou une interface qui ignore les promesses.


Question

Que se passe-t-il si on passe une promesse à resolve ?

Elle est adoptée : la promesse extérieure prend l'issue de la promesse intérieure au lieu d'un objet imbriqué. C'est aussi ce qui rend le return d'une promesse transparent dans un then, et ce qui interdit d'obtenir une promesse de promesse.


Question

L'exécuteur peut-il être marqué async ?

Techniquement oui, mais c'est un défaut connu. Les erreurs lancées dans un exécuteur async deviennent le rejet de la promesse interne, qui n'est reliée à rien, et disparaissent du diagnostic. Gardez un exécuteur ordinaire et sortez le travail asynchrone.

Termes connexes

Découvrez notre glossaire JavaScript

Tous les mots de JavaScript expliqués simplement : mots-clés, objets natifs, méthodes, erreurs et concepts. Définitions claires et exemples qui tournent, pour apprendre et pour se dépanner.

Partager cet article

Tu veux nous aider ? Fais un lien vers cet article sur tes réseaux ou encore mieux : sur ton site, dans un article ou dans ta newsletter.