Promises vs Observables : guide complet, différences et usages en JavaScript

Promises vs Observables : la question revient sans cesse chez les développeurs JavaScript et TypeScript. Mais quelles sont les différences entre ces deux approches de la programmation asynchrone ? Faut-il privilégier l’une ou l’autre selon le contexte ? Pour vous aider à y voir plus clair, ce guide complet vous explique tout, avec des exemples concrets, un tableau comparatif, des images explicatives et des conseils pour bien choisir selon vos besoins.

Différences entre Promises et Observables

Fonctionnement des Promises en JavaScript

Une Promise représente la valeur éventuelle d’une opération asynchrone. Concrètement, elle peut être dans l’un des trois états suivants :

  • Pending (en attente)
  • Fulfilled (résolue)
  • Rejected (rejetée)
// Exemple de Promise
const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Données récupérées");
    }, 1000);
  });
};

fetchData().then(data => console.log(data));

Caractéristiques des Promises :

  • ✅ Émettent une seule valeur
  • ✅ Sont “eager” (s’exécutent immédiatement)
  • ✅ Ne peuvent pas être annulées
  • ✅ Gèrent bien les opérations asynchrones simples

Observables et flux de données avec RxJS

Un Observable est un flux de données qui peut émettre plusieurs valeurs au fil du temps. Par ailleurs, il fait partie de la bibliothèque RxJS.

// Exemple d'Observable
import { Observable } from 'rxjs';

const dataStream = new Observable(observer => {
  observer.next("Première valeur");
  observer.next("Deuxième valeur");
  
  setTimeout(() => {
    observer.next("Troisième valeur");
    observer.complete();
  }, 1000);
});

dataStream.subscribe(data => console.log(data));

Caractéristiques des Observables :

  • ✅ Peuvent émettre plusieurs valeurs
  • ✅ Sont “lazy” (ne s’exécutent que lors de la souscription)
  • ✅ Peuvent être annulés (unsubscribe)
  • ✅ Offrent de nombreux opérateurs pour la transformation des données
  • ✅ Parfaits pour les flux de données en temps réel

Tableau comparatif : Promises, Observables et flux asynchrones

Aspect Promise Observable
Nombre de valeurs Une seule Multiples
Exécution Eager (immédiate) Lazy (à la souscription)
Annulation Impossible Possible (unsubscribe)
Opérateurs Limités (then, catch) Nombreux (map, filter, etc.)
Cas d’usage Requêtes HTTP simples Flux de données, événements

BehaviorSubject et gestion d’état réactive

Présentation du BehaviorSubject

Un BehaviorSubject est un type spécial d’Observable qui présente plusieurs particularités :

  • Il stocke la dernière valeur émise
  • Il émet immédiatement cette valeur aux nouveaux souscripteurs
  • Il nécessite une valeur initiale

Exemple d’utilisation de BehaviorSubject pour les notifications

import { BehaviorSubject } from 'rxjs';

// Initialisation avec une valeur par défaut
const notification = new BehaviorSubject<string>('Aucune notification');

Détail de l’initialisation d’un BehaviorSubject

notification = new BehaviorSubject<string>('Aucune notification');

Décortiquons cette ligne :

  1. notification : Variable qui stockera notre BehaviorSubject
  2. new BehaviorSubject : Crée une nouvelle instance de BehaviorSubject
  3. <string> : Type TypeScript indiquant que les valeurs émises seront des chaînes
  4. 'Aucune notification' : Valeur initiale obligatoire

Avantages de BehaviorSubject pour la gestion des notifications

class NotificationService {
  private notification = new BehaviorSubject<string>('');
  
  // Observable public pour la souscription
  public notification$ = this.notification.asObservable();
  
  // Méthode pour émettre une nouvelle notification
  showNotification(message: string) {
    this.notification.next(message);
  }
  
  // Méthode pour obtenir la dernière notification
  getCurrentNotification(): string {
    return this.notification.value;
  }
}

Avantages :

  • Persistance : La dernière notification est toujours disponible
  • Réactivité : Tous les composants souscripteurs sont notifiés automatiquement
  • Synchronisation : Les nouveaux composants reçoivent immédiatement l’état actuel
  • Gestion d’état : Parfait pour maintenir l’état global des notifications

Cas pratique : notifications réactives avec BehaviorSubject

// Service de notification
@Injectable({
  providedIn: 'root'
})
export class NotificationService {
  private notificationSubject = new BehaviorSubject<{
    message: string;
    type: 'success' | 'error' | 'info';
  }>({ message: '', type: 'info' });
  
  public notification$ = this.notificationSubject.asObservable();
  
  showSuccess(message: string) {
    this.notificationSubject.next({ message, type: 'success' });
  }
  
  showError(message: string) {
    this.notificationSubject.next({ message, type: 'error' });
  }
  
  clearNotification() {
    this.notificationSubject.next({ message: '', type: 'info' });
  }
}

// Composant qui affiche les notifications
@Component({
  selector: 'app-notification',
  template: `
    <div *ngIf="notification.message" 
         [class]="'notification ' + notification.type">
      {{ notification.message }}
    </div>
  `
})
export class NotificationComponent implements OnInit, OnDestroy {
  notification = { message: '', type: 'info' as const };
  private subscription: Subscription;
  
  constructor(private notificationService: NotificationService) {}
  
  ngOnInit() {
    this.subscription = this.notificationService.notification$
      .subscribe(notification => this.notification = notification);
  }
  
  ngOnDestroy() {
    this.subscription?.unsubscribe();
  }
}

Bonnes pratiques pour la programmation asynchrone en JavaScript

Pour les Promises

  • Utilisez pour les opérations asynchrones simples (requêtes HTTP)
  • Gérez toujours les erreurs avec .catch()
  • Préférez async/await pour une syntaxe plus claire

Pour les Observables

  • Utilisez pour les flux de données complexes
  • N’oubliez jamais de vous désabonner pour éviter les fuites mémoire
  • Utilisez les opérateurs RxJS pour transformer les données

Pour les BehaviorSubject

  • Initialisez toujours avec une valeur par défaut cohérente
  • Encapsulez dans un service pour centraliser la logique
  • Exposez un Observable public plutôt que le BehaviorSubject directement

Conclusion : Promises, Observables ou BehaviorSubject ?

Le choix entre Promises et Observables dépend du contexte. Ainsi :

  • Promises pour les opérations simples et uniques
  • Observables pour les flux de données complexes
  • BehaviorSubject pour la gestion d’état réactive avec persistance

L’utilisation de notification = new BehaviorSubject() pour les notifications offre une solution élégante pour maintenir et distribuer l’état des notifications dans toute l’application, garantissant que tous les composants restent synchronisés avec les dernières informations.


Pour aller plus loin : Programmation réactive et gestion d’état en JavaScript

Ressources externes utiles :

Comparatif Promises et Observables en JavaScript et TypeScript

Schéma différences Promises, Observables et flux asynchrones

Cet article fait partie de la série sur la programmation réactive et la gestion d’état en JavaScript/TypeScript.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *