JavaScript Promises : Guide Complet de la Programmation Asynchrone

Maitrisez les Promises JavaScript : creation, chainage, gestion d'erreurs, Promise.all et patterns avances pour un code asynchrone elegant.

Mahmoud DEVO
Mahmoud DEVO
December 27, 2025 12 min read
JavaScript Promises : Guide Complet de la Programmation Asynchrone

JavaScript Promises : Guide Complet de la Programmation Asynchrone

La programmation asynchrone est au coeur du developpement JavaScript moderne. Que vous fassiez des appels API, lisiez des fichiers ou gerez des interactions utilisateur, vous rencontrerez inevitablement du code asynchrone. Les Promises (promesses) representent l’evolution majeure qui a transforme la facon dont nous ecrivons ce code.

Pourquoi les Promises ? Le Probleme du Callback Hell

Avant les Promises, le code asynchrone en JavaScript reposait entierement sur les callbacks. Cela fonctionnait pour des cas simples, mais degenerait rapidement en ce qu’on appelle le “Callback Hell” ou “Pyramid of Doom” :

// Le redoutable Callback Hell
getUserData(userId, function(user) {
  getOrders(user.id, function(orders) {
    getOrderDetails(orders[0].id, function(details) {
      getShippingInfo(details.shippingId, function(shipping) {
        updateUI(user, orders, details, shipping, function() {
          console.log('Tout est termine !');
        }, function(error) {
          console.error('Erreur UI:', error);
        });
      }, function(error) {
        console.error('Erreur shipping:', error);
      });
    }, function(error) {
      console.error('Erreur details:', error);
    });
  }, function(error) {
    console.error('Erreur orders:', error);
  });
}, function(error) {
  console.error('Erreur user:', error);
});

Ce code presente plusieurs problemes majeurs :

  • Lisibilite catastrophique : l’indentation excessive rend le code difficile a suivre
  • Gestion d’erreurs fragmentee : chaque callback necessite sa propre gestion d’erreur
  • Difficulte de maintenance : ajouter ou modifier une etape devient un cauchemar
  • Inversion de controle : vous confiez l’execution de votre code a une fonction externe

Les Promises resolvent elegamment tous ces problemes en offrant une interface standardisee pour le code asynchrone.


Anatomie d’une Promise

Une Promise est un objet qui represente le resultat eventuel d’une operation asynchrone. Elle peut etre dans l’un des trois etats suivants :

Les Trois Etats d’une Promise

EtatDescriptionTransition possible
pendingEtat initial, l’operation est en coursvers fulfilled ou rejected
fulfilledL’operation a reussi avec une valeurEtat final
rejectedL’operation a echoue avec une raisonEtat final

Une fois qu’une Promise atteint l’etat fulfilled ou rejected, elle est dite “settled” (reglee) et ne peut plus changer d’etat. C’est une garantie fondamentale des Promises.

// Visualisation des etats
const pendingPromise = new Promise(() => {}); // Reste pending indefiniment

const fulfilledPromise = new Promise((resolve) => {
  resolve('Succes !'); // Passe a fulfilled
});

const rejectedPromise = new Promise((_, reject) => {
  reject(new Error('Echec !')); // Passe a rejected
});

Le Constructeur Promise

Le constructeur Promise prend une fonction executor avec deux parametres :

const maPromise = new Promise((resolve, reject) => {
  // resolve(valeur) - appeler pour reussir la promesse
  // reject(raison) - appeler pour echouer la promesse
});

Regles importantes de l’executor :

  1. L’executor s’execute immediatement et de maniere synchrone
  2. Seul le premier appel a resolve() ou reject() compte
  3. Les appels suivants sont ignores silencieusement
  4. Si l’executor lance une exception, la Promise est automatiquement rejetee
// Exemple : seul le premier appel compte
const promise = new Promise((resolve, reject) => {
  resolve('Premier'); // Cette valeur sera utilisee
  resolve('Deuxieme'); // Ignore
  reject('Erreur'); // Ignore aussi
});

promise.then(value => console.log(value)); // "Premier"

Les Methodes d’Instance : then(), catch(), finally()

then() - Gerer le succes (et l’echec)

La methode then() accepte jusqu’a deux callbacks :

promise.then(
  (valeur) => { /* succes */ },
  (raison) => { /* echec */ }
);
// Exemple pratique
fetchUser(123)
  .then(
    user => console.log('Utilisateur:', user.name),
    error => console.error('Erreur:', error.message)
  );

catch() - Gerer les erreurs

catch() est un raccourci pour then(undefined, onRejected) :

promise
  .then(valeur => traiterValeur(valeur))
  .catch(erreur => gererErreur(erreur));

Avantage de catch() sur le second argument de then() :

// Probleme : le handler d'erreur de then() ne capture pas
// les erreurs du handler de succes
promise.then(
  value => { throw new Error('Oups !'); },
  error => console.log('Pas capture !') // Cette erreur n'est PAS capturee ici
);

// Solution : utiliser catch() a la fin
promise
  .then(value => { throw new Error('Oups !'); })
  .catch(error => console.log('Capture !', error)); // Capture toutes les erreurs

finally() - Nettoyage garanti

finally() s’execute que la Promise soit fulfilled ou rejected, sans modifier le resultat :

let loadingData = true;

fetch('/api/data')
  .then(response => response.json())
  .then(data => processData(data))
  .catch(error => showError(error))
  .finally(() => {
    loadingData = false;
    hideLoadingSpinner();
  });

Caracteristiques de finally() :

  • Ne recoit aucun argument
  • Ne modifie pas la valeur de resolution
  • Ne modifie pas la raison de rejet
  • Propage la Promise originale (sauf si elle lance une erreur)

Creation de Promises

Promise.resolve() et Promise.reject()

Ces methodes statiques creent des Promises deja reglees :

// Creer une Promise fulfilled
const resolved = Promise.resolve(42);
resolved.then(value => console.log(value)); // 42

// Creer une Promise rejected
const rejected = Promise.reject(new Error('Echec'));
rejected.catch(error => console.error(error.message)); // "Echec"

Cas special : Promise.resolve() avec une Promise

Si vous passez une Promise a Promise.resolve(), elle est retournee telle quelle :

const original = new Promise(resolve => setTimeout(() => resolve('OK'), 1000));
const wrapped = Promise.resolve(original);

console.log(original === wrapped); // true - meme reference !

Cas special : thenable

Un “thenable” est un objet avec une methode then(). Promise.resolve() le convertit en vraie Promise :

const thenable = {
  then(resolve, reject) {
    setTimeout(() => resolve('Je suis un thenable !'), 1000);
  }
};

Promise.resolve(thenable)
  .then(value => console.log(value)); // "Je suis un thenable !"

Promisifier des Callbacks

Le pattern de “promisification” transforme des API basees sur callbacks en Promises :

// API callback classique (style Node.js)
function readFileCallback(path, callback) {
  // callback(error, data)
}

// Version promisifiee
function readFilePromise(path) {
  return new Promise((resolve, reject) => {
    readFileCallback(path, (error, data) => {
      if (error) {
        reject(error);
      } else {
        resolve(data);
      }
    });
  });
}

// Utilisation
readFilePromise('/config.json')
  .then(data => JSON.parse(data))
  .then(config => console.log(config))
  .catch(error => console.error('Erreur lecture:', error));

Fonction utilitaire generique de promisification :

function promisify(fn) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      fn(...args, (error, result) => {
        if (error) {
          reject(error);
        } else {
          resolve(result);
        }
      });
    });
  };
}

// Utilisation
const readFile = promisify(fs.readFile);
const data = await readFile('file.txt', 'utf8');

setTimeout Promisifie : La Fonction delay()

Une des promisifications les plus utiles est celle de setTimeout :

function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// Utilisation simple
delay(2000).then(() => console.log('2 secondes ecoulees !'));

// Avec async/await
async function exemple() {
  console.log('Debut');
  await delay(1000);
  console.log('1 seconde plus tard');
  await delay(1000);
  console.log('2 secondes plus tard');
}

Version avancee avec valeur et annulation :

function delay(ms, value) {
  let timeoutId;
  const promise = new Promise((resolve) => {
    timeoutId = setTimeout(() => resolve(value), ms);
  });

  promise.cancel = () => clearTimeout(timeoutId);
  return promise;
}

// Utilisation avec valeur
const result = await delay(1000, 'Valeur retournee');
console.log(result); // "Valeur retournee"

// Avec annulation
const delayedAction = delay(5000, 'Action');
delayedAction.cancel(); // Annule le timeout

Chainage de Promises

Le chainage est une des fonctionnalites les plus puissantes des Promises. Chaque appel a then(), catch() ou finally() retourne une nouvelle Promise.

Retourner des Valeurs vs Retourner des Promises

// Retourner une valeur simple
Promise.resolve(1)
  .then(value => value + 1)     // Retourne 2
  .then(value => value * 2)     // Retourne 4
  .then(value => console.log(value)); // 4

// Retourner une Promise
Promise.resolve(1)
  .then(value => Promise.resolve(value + 1)) // Retourne Promise<2>
  .then(value => delay(1000).then(() => value * 2)) // Attend puis retourne 4
  .then(value => console.log(value)); // 4 (apres 1 seconde)

Regle d’or : Si votre callback retourne une Promise, la chaine attend automatiquement sa resolution.

Exemple Pratique de Chainage

// Scenario : authentification et chargement de donnees utilisateur
function loginUser(credentials) {
  return fetch('/api/login', {
    method: 'POST',
    body: JSON.stringify(credentials)
  })
  .then(response => {
    if (!response.ok) {
      throw new Error(`Erreur HTTP: ${response.status}`);
    }
    return response.json();
  })
  .then(data => {
    localStorage.setItem('token', data.token);
    return data.userId;
  })
  .then(userId => {
    return fetch(`/api/users/${userId}`);
  })
  .then(response => response.json())
  .then(user => {
    console.log('Utilisateur connecte:', user.name);
    return user;
  })
  .catch(error => {
    console.error('Erreur de connexion:', error.message);
    throw error; // Re-lancer pour permettre au code appelant de gerer
  });
}

Propagation des Erreurs

Les erreurs se propagent a travers la chaine jusqu’au premier catch() :

Promise.resolve('debut')
  .then(value => {
    console.log('Etape 1:', value);
    return 'etape 1 terminee';
  })
  .then(value => {
    console.log('Etape 2:', value);
    throw new Error('Erreur a l\'etape 2 !');
  })
  .then(value => {
    // Cette etape est IGNOREE
    console.log('Etape 3:', value);
  })
  .then(value => {
    // Cette etape aussi est IGNOREE
    console.log('Etape 4:', value);
  })
  .catch(error => {
    console.error('Erreur capturee:', error.message);
    return 'recuperation'; // La chaine peut continuer
  })
  .then(value => {
    console.log('Etape finale:', value); // "recuperation"
  });

Methodes Statiques Avancees

Promise.all() - Parallelisme Total

Execute plusieurs Promises en parallele et attend que toutes soient resolues :

const promise1 = fetch('/api/users');
const promise2 = fetch('/api/posts');
const promise3 = fetch('/api/comments');

Promise.all([promise1, promise2, promise3])
  .then(([usersRes, postsRes, commentsRes]) => {
    // Les trois requetes sont terminees
    return Promise.all([
      usersRes.json(),
      postsRes.json(),
      commentsRes.json()
    ]);
  })
  .then(([users, posts, comments]) => {
    console.log('Utilisateurs:', users.length);
    console.log('Posts:', posts.length);
    console.log('Commentaires:', comments.length);
  })
  .catch(error => {
    // Si UNE SEULE Promise echoue, tout echoue
    console.error('Une requete a echoue:', error);
  });

Comportement de Promise.all() :

  • Resout avec un tableau de toutes les valeurs (dans l’ordre)
  • Rejette des qu’UNE Promise echoue (fail-fast)
  • Les autres Promises continuent mais leurs resultats sont ignores

Promise.race() - Le Premier Gagne

Retourne le resultat de la premiere Promise a se regler (fulfilled ou rejected) :

// Exemple : timeout pour une requete
function fetchWithTimeout(url, timeout = 5000) {
  return Promise.race([
    fetch(url),
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error('Timeout !')), timeout)
    )
  ]);
}

fetchWithTimeout('/api/slow-endpoint', 3000)
  .then(response => console.log('Reponse recue !'))
  .catch(error => console.error(error.message)); // "Timeout !" si > 3s

Promise.allSettled() - Attendre Tout le Monde

Attend que toutes les Promises soient reglees, sans echouer si certaines sont rejetees :

const promises = [
  fetch('/api/users'),
  fetch('/api/nonexistent'), // Cette URL n'existe pas
  fetch('/api/posts')
];

Promise.allSettled(promises)
  .then(results => {
    results.forEach((result, index) => {
      if (result.status === 'fulfilled') {
        console.log(`Promise ${index}: Succes`, result.value);
      } else {
        console.log(`Promise ${index}: Echec`, result.reason);
      }
    });
  });

// Sortie :
// Promise 0: Succes Response {...}
// Promise 1: Echec Error: 404
// Promise 2: Succes Response {...}

Cas d’usage : Quand vous voulez tenter plusieurs operations et traiter les resultats individuellement, meme si certaines echouent.

Promise.any() - Le Premier Succes

Retourne la premiere Promise a etre fulfilled (ignore les rejets) :

// Exemple : essayer plusieurs serveurs miroirs
const mirrors = [
  'https://mirror1.example.com/file.zip',
  'https://mirror2.example.com/file.zip',
  'https://mirror3.example.com/file.zip'
];

Promise.any(mirrors.map(url => fetch(url)))
  .then(response => {
    console.log('Telechargement depuis:', response.url);
  })
  .catch(error => {
    // AggregateError si TOUTES les Promises ont echoue
    console.error('Tous les miroirs ont echoue');
    console.error(error.errors); // Tableau de toutes les erreurs
  });

Tableau Comparatif des Methodes Statiques

MethodeResout quand…Rejette quand…
Promise.all()Toutes fulfilledUne seule rejected
Promise.race()Premiere settledPremiere rejected
Promise.allSettled()Toutes settledJamais
Promise.any()Premiere fulfilledToutes rejected

Bonnes Pratiques

1. Toujours Retourner dans les Callbacks

// MAUVAIS - la Promise n'est pas chainee
promise.then(value => {
  anotherPromise(value); // Oubli du return !
});

// BON - la Promise est correctement chainee
promise.then(value => {
  return anotherPromise(value);
});

// ENCORE MIEUX - syntaxe fleche implicite
promise.then(value => anotherPromise(value));

2. Toujours Gerer les Erreurs

// MAUVAIS - erreur silencieuse
fetch('/api/data')
  .then(response => response.json());

// BON - erreur geree
fetch('/api/data')
  .then(response => response.json())
  .catch(error => {
    console.error('Erreur:', error);
    // Notifier l'utilisateur, logger, etc.
  });

3. Eviter l’Anti-Pattern Promise Constructor

// MAUVAIS - Promise constructor inutile
function getData() {
  return new Promise((resolve, reject) => {
    fetch('/api/data')
      .then(response => resolve(response.json()))
      .catch(error => reject(error));
  });
}

// BON - retourner directement la Promise
function getData() {
  return fetch('/api/data').then(response => response.json());
}

4. Utiliser async/await pour la Lisibilite

// Avec Promises chainee
function getFullUserData(userId) {
  return getUser(userId)
    .then(user => getOrders(user.id).then(orders => ({ user, orders })))
    .then(({ user, orders }) =>
      getOrderDetails(orders[0].id).then(details => ({ user, orders, details }))
    );
}

// Avec async/await - beaucoup plus lisible
async function getFullUserData(userId) {
  const user = await getUser(userId);
  const orders = await getOrders(user.id);
  const details = await getOrderDetails(orders[0].id);
  return { user, orders, details };
}

5. Paralleliser quand c’est Possible

// LENT - sequentiel (inutilement)
async function loadData() {
  const users = await fetch('/api/users');
  const posts = await fetch('/api/posts');
  const comments = await fetch('/api/comments');
  // Temps total: T(users) + T(posts) + T(comments)
}

// RAPIDE - parallele
async function loadData() {
  const [users, posts, comments] = await Promise.all([
    fetch('/api/users'),
    fetch('/api/posts'),
    fetch('/api/comments')
  ]);
  // Temps total: max(T(users), T(posts), T(comments))
}

Pieges Courants a Eviter

1. Oublier de Retourner la Promise

// BUG : la chaine est brisee
function process() {
  return fetch('/api/data')
    .then(response => {
      response.json(); // OUBLI DU RETURN !
    })
    .then(data => {
      console.log(data); // undefined !
    });
}

2. Erreurs Avalees Silencieusement

// BUG : erreur jamais loggee
function fetchData() {
  return fetch('/api/data')
    .then(response => response.json())
    .catch(error => {
      // L'erreur est "avalee" - la Promise est maintenant fulfilled avec undefined
    });
}

// CORRECTION
function fetchData() {
  return fetch('/api/data')
    .then(response => response.json())
    .catch(error => {
      console.error('Erreur:', error);
      throw error; // Re-lancer pour permettre au code appelant de gerer
    });
}

3. Creer des Promises Inutilement

// INUTILE - Promise deja une Promise
async function getData() {
  return new Promise(resolve => {
    resolve(fetch('/api/data')); // fetch retourne deja une Promise !
  });
}

// CORRECT
async function getData() {
  return fetch('/api/data');
}

4. Ignorer les Rejections non Gerees

// Provoque un UnhandledPromiseRejection
Promise.reject(new Error('Non geree'));

// Toujours gerer
Promise.reject(new Error('Geree'))
  .catch(error => console.error(error));

5. forEach avec async ne Fonctionne pas comme Attendu

// BUG : forEach n'attend pas les Promises
async function processItems(items) {
  items.forEach(async item => {
    await processItem(item); // Ces awaits ne bloquent pas forEach !
  });
  console.log('Termine ?'); // S'affiche AVANT que tout soit traite !
}

// CORRECTION avec for...of
async function processItems(items) {
  for (const item of items) {
    await processItem(item);
  }
  console.log('Vraiment termine !');
}

// OU en parallele avec Promise.all
async function processItems(items) {
  await Promise.all(items.map(item => processItem(item)));
  console.log('Termine en parallele !');
}

Conclusion

Les Promises ont revolutionne la programmation asynchrone en JavaScript. Elles offrent :

  • Une API standardisee pour gerer les operations asynchrones
  • Un chainage elegant qui elimine le callback hell
  • Une gestion d’erreurs centralisee avec propagation automatique
  • Des methodes puissantes (all, race, allSettled, any) pour orchestrer plusieurs operations

Recapitulatif : Callbacks vs Promises vs Async/Await

AspectCallbacksPromisesAsync/Await
LisibiliteFaible (pyramid of doom)Moyenne (chainage)Excellente (synchrone)
Gestion erreursFragmenteeCentralisee (catch)try/catch natif
ParallelismeComplexePromise.all()await Promise.all()
DebugDifficileMoyenFacile (stack traces)
Support navigateurUniverselES6+ES2017+

Conseil final : Utilisez async/await pour le code metier (meilleure lisibilite) et les Promises brutes pour les utilitaires et bibliotheques. Maitrisez les deux, car async/await n’est qu’une surcouche syntaxique des Promises.

Vous etes maintenant armes pour ecrire du code asynchrone elegant, maintenable et robuste !

Advertisement

In-Article Ad

Dev Mode

Share this article

Mahmoud DEVO

Mahmoud DEVO

Senior Full-Stack Developer

I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.

Enjoyed this article?

Subscribe to get more tech content delivered to your inbox.

Related Articles