HOWTO · Angular

Observable Angular VS. Promise

Dans cet article, nous expliquerons ce qui est Observable et Promise dans Angular. De plus, nous expliquerons la différence fondamentale entre observable et Promise.

Sur cette page

Choisissez une Promise pour obtenir un seul résultat futur, et un Observable lorsque la tâche se prête naturellement à des valeurs émises au fil du temps. Angular ne rend pas ces abstractions interchangeables : le bon choix dépend du comportement dont votre code a besoin.

Qu’est-ce qu’une Promise dans Angular

Une Promise est un objet JavaScript qui représente un résultat futur unique : sa résolution avec une valeur ou son rejet avec une raison. Elle passe de l’état en attente à l’état résolu ou rejeté. Ses méthodes then, catch et finally permettent d’enchaîner des opérations et de gérer leurs résultats. Une Promise ne se règle qu’une fois ; une Promise résolue n’émet pas ensuite d’autres valeurs.

L’exécuteur d’une Promise démarre au moment de sa construction. L’API Promise ne fournit pas de mécanisme intégré d’annulation ni de nouvelle tentative, mais le code de l’application peut mettre en œuvre un traitement tenant compte de l’annulation ou une logique de nouvelle tentative autour d’une Promise. Les longues chaînes peuvent aussi devenir difficiles à suivre à mesure que les processus se complexifient.

Qu’est-ce qu’un Observable dans Angular

Un Observable représente un flux susceptible d’émettre zéro ou plusieurs valeurs, puis de se terminer ou de signaler une erreur. Il peut aussi rester ouvert indéfiniment sans envoyer l’une ou l’autre de ces notifications terminales. Dans Angular, HttpClient renvoie des Observables RxJS. Le traitement d’un Observable commence souvent lors de la souscription, mais cela dépend de la façon dont il a été créé. Les opérateurs RxJS peuvent transformer et combiner des flux. La désinscription arrête la réception des notifications et peut annuler le traitement sous-jacent si son producteur prend en charge l’annulation.

Un Observable ne garantit pas à lui seul l’annulation, les nouvelles tentatives ou de meilleures performances dans tous les cas : ces comportements dépendent du producteur et des opérateurs utilisés. RxJS apporte des outils utiles, mais les développeurs qui découvrent les flux doivent prévoir un temps d’apprentissage.

Principales différences entre Promise et Observable dans Angular

Critère Promise Observable
Valeurs Une valeur en cas de résolution ou une raison de rejet Zéro ou plusieurs valeurs next ; le flux peut se terminer, signaler une erreur ou rester ouvert sans envoyer de notification terminale
Démarrage L’exécuteur s’exécute à la construction Le traitement commence souvent à la souscription ; le moment dépend de la création
Erreurs À gérer avec catch ou un gestionnaire de rejet À gérer dans la souscription ou avec des opérateurs comme catchError
Annulation Aucune méthode d’annulation intégrée La désinscription arrête l’observation et peut annuler les traitements pris en charge
Cas adapté Un résultat, par exemple un calcul simple Des événements, des valeurs changeantes ou un traitement qui bénéficie de la composition RxJS
Nouvelle tentative Aucune nouvelle tentative intégrée ; rappelez vous-même la fonction productrice Des opérateurs comme retry réabonnent à la source après un échec
Partage Tous les gestionnaires observent le même résultat unique déjà réglé Un Observable froid exécute à nouveau son producteur pour chaque abonné ; partagez explicitement une source chaude lorsqu’une seule exécution doit servir de nombreux abonnés

Pour une requête HTTP dans Angular, utilisez l’Observable de HttpClient lorsque vous avez besoin de composer des opérations RxJS ou de gérer l’annulation. La réponse peut contenir un tableau, mais ce tableau ne nécessite pas un Observable parce qu’il contient plusieurs éléments : la requête produit toujours une seule valeur de réponse. Une Promise peut aussi représenter cette réponse. Choisissez l’abstraction qui correspond à la tâche et aux API environnantes.

Annulation, paresse et nouvelles tentatives en détail

L’annulation illustre la différence pratique la plus nette. Dans un champ de recherche qui cherche au fil de la frappe, chaque nouvelle frappe devrait abandonner la requête du terme précédent. Avec un Observable de HttpClient, passer à une nouvelle requête avec switchMap désinscrit la précédente, et HttpClient annule alors cette requête en cours. Une Promise n’offre pas de bascule équivalente : une fois démarré, le traitement sous-jacent continue, sauf si l’API appelée fournit son propre mécanisme d’annulation, comme un AbortController pour fetch, distinct de la Promise elle-même.

Le moment d’exécution diffère également. Une Promise démarre son traitement dès sa construction, qu’un code attache ou non un gestionnaire ; créer la Promise est donc déjà un effet de bord. Un Observable froid est au contraire paresseux : le créer ne fait que décrire le traitement, chaque appel à subscribe exécute à nouveau le producteur, et deux abonnés peuvent donc déclencher deux exécutions distinctes. Le code qui attend une seule exécution partagée doit le dire explicitement, par exemple avec un opérateur de partage ou en stockant une Promise.

Les nouvelles tentatives et la composition suivent le même schéma. Une Promise rejetée est terminée ; se rétablir signifie rappeler la fonction productrice dans votre propre boucle. Un Observable peut à la place se réabonner à sa source avec l’opérateur retry, et les schémas temporels qui exigeraient autour d’une Promise des minuteries et des drapeaux écrits à la main, comme attendre une pause dans la frappe avec debounceTime ou ignorer une saisie inchangée avec distinctUntilChanged, sont de simples opérateurs dans RxJS.

Choisir en pratique

Choisissez une Promise lorsque le résultat est réellement unique et jetable : un calcul ponctuel, un SDK tiers qui renvoie déjà une Promise, ou un flux linéaire court qui se lit mieux avec async et await et n’a jamais besoin d’être annulé. Choisissez un Observable lorsque la source est un flux par nature, comme des réponses HttpClient que vous voulez composer ou annuler, des paramètres de route, des changements de valeurs de formulaire, des événements DOM ou un WebSocket, et chaque fois que les abonnés tardifs, les valeurs répétées ou le nettoyage comptent. En cas de doute, conservez le type que l’API productrice renvoie déjà au lieu de convertir à chaque frontière.

Convertir entre Promise et Observable

Les deux modèles interopèrent, mais la conversion n’est pas gratuite. Dans RxJS, from enveloppe une Promise dans un Observable qui émet la valeur réglée une fois, tandis que firstValueFrom et lastValueFrom transforment un Observable en Promise pour le code en async et await. Convertir ainsi un Observable de HttpClient conserve la réponse unique, mais abandonne l’annulation que la désinscription offrait et met fin à toute autre composition d’opérateurs à cet endroit. Convertissez à une frontière claire, comme le bord d’un service, plutôt qu’au sein d’une logique de flux réutilisable.

Erreurs fréquentes

  • Traiter un tableau contenu dans une seule réponse HTTP comme des émissions multiples. La requête n’émet toujours qu’une fois ; seule une source qui se répète réellement émet de façon répétée.
  • Convertir une requête en Promise puis attendre que quitter la page l’annule. Sans souscription à désinscrire, rien ne signale au producteur de s’arrêter.
  • S’abonner dans un composant à longue durée de vie sans stratégie de nettoyage, comme le pipe async ou takeUntilDestroyed, si bien que des souscriptions obsolètes continuent de recevoir des valeurs après la disparition de la vue.
  • Attendre qu’une Promise déjà réglée prenne plus tard une valeur plus récente. Une Promise mémorise son résultat unique ; relire l’état actuel exige un nouvel appel ou un flux.

Exemple de Promise et d’Observable dans Angular

Cet exemple JavaScript autonome utilise un petit Observable pédagogique pour illustrer la souscription et les émissions multiples. Il ne s’agit pas de l’implémentation RxJS utilisée par Angular. L’Observable émet queued et saved pendant la souscription ; le callback de la Promise s’exécute ensuite dans une microtâche. Les libellés queued et saved restent en anglais dans l’exemple.

Exécutez cet exemple autonome :

class Observable {
  constructor(producer) {
    this.producer = producer;
  }

  subscribe(next) {
    const teardown = this.producer(next);
    return { unsubscribe: teardown };
  }
}

const promise = Promise.resolve("saved");
const updates = new Observable((next) => {
  next("queued");
  next("saved");
  return () => {};
});

console.log("before subscription");
updates.subscribe((value) => console.log(`Observable: ${value}`));
promise.then((value) => console.log(`Promise: ${value}`));
console.log("after scheduling");
before subscription
Observable: queued
Observable: saved
after scheduling
Promise: saved

L’Observable pédagogique appelle son producteur lors de la souscription et émet deux valeurs ; la Promise ne se règle qu’une fois, et son callback s’exécute dans une microtâche ultérieure. Dans une application, utilisez Observable de RxJS et ses API de souscription. Choisissez une Promise pour un résultat unique, et un Observable lorsque la composition de flux, les émissions répétées ou la gestion des souscriptions conviennent à la tâche.

Exemple : Promise immédiate, Observable paresseux et annulable

Ce second exemple autonome utilise le même Observable pédagogique pour illustrer les lignes du tableau consacrées au moment d’exécution et à l’annulation. L’exécuteur de la Promise s’exécute avant que quiconque attache un gestionnaire, tandis que le producteur de l’Observable attend subscribe et exécute son nettoyage lorsque l’abonné se désinscrit après la deuxième impulsion. Le callback de la Promise s’exécute à nouveau comme une microtâche, avant la première impulsion de la minuterie. Les libellés de l’exemple restent en anglais.

class Observable {
  constructor(producer) {
    this.producer = producer;
  }

  subscribe(next) {
    const teardown = this.producer(next);
    return { unsubscribe: teardown };
  }
}

const promise = new Promise((resolve) => {
  console.log("Promise executor: runs at construction");
  resolve("done");
});

const ticks = new Observable((next) => {
  console.log("Observable producer: runs at subscription");
  let count = 0;
  const id = setInterval(() => next(++count), 10);
  return () => {
    clearInterval(id);
    console.log("teardown: interval cleared");
  };
});

console.log("before subscription");
const sub = ticks.subscribe((value) => {
  console.log(`tick ${value}`);
  if (value === 2) sub.unsubscribe();
});
promise.then((value) => console.log(`Promise: ${value}`));
Promise executor: runs at construction
before subscription
Observable producer: runs at subscription
Promise: done
tick 1
tick 2
teardown: interval cleared

L’exécution confirme les trois comportements dans l’ordre : l’exécuteur s’est déclenché à la construction, le producteur est resté inactif jusqu’à la souscription, et la désinscription a exécuté le nettoyage qui a supprimé l’intervalle, si bien qu’aucune autre impulsion n’arrive.