Observables vs Promises in JavaScript
January 4, 2026
Observables vs Promises in JavaScript
Both Observables and Promises are used to handle asynchronous operations in JavaScript, but they serve different purposes and have distinct characteristics. Understanding their differences is crucial for choosing the right tool for your use case.
Key Differences
| Aspect | Promises | Observables |
| ------------------ | -------------------------------------------- | -------------------------------------------------- |
| Value Emission | Single value, one-time resolution | Multiple values over time, continuous emission |
| Execution | Executes immediately on creation | Lazy execution, starts only on subscription |
| Cancellation | Cannot be cancelled once started | Supports cancellation via unsubscribe() |
| Transformation | Basic chaining with .then() and .catch() | Rich operator ecosystem (map, filter, merge, etc.) |
| Error Handling | Single error, then terminates | Can handle errors and continue emitting |
| Use Case | One-time async operations | Streams of data, events, real-time updates |
Promises
A Promise represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
Characteristics
- Single Value: Resolves or rejects once
- Eager Execution: Starts executing immediately when created
- No Cancellation: Cannot be cancelled once started
- Simple API:
.then(),.catch(),.finally()
Example
// Promise Example
const promiseExample = () => {
const promise = new Promise((resolve) => {
setTimeout(() => resolve("Promise resolved!"), 1000);
});
promise.then(console.log); // "Promise resolved!" after 1 second
};
promiseExample();
Promise Chaining
fetch("/api/user")
.then((response) => response.json())
.then((user) => fetch(`/api/posts/${user.id}`))
.then((response) => response.json())
.then((posts) => console.log(posts))
.catch((error) => console.error(error));
Observables
Observables are a pattern for handling asynchronous data streams. They're part of the RxJS library and are heavily used in Angular.
Characteristics
- Multiple Values: Can emit multiple values over time
- Lazy Execution: Only executes when subscribed to
- Cancellable: Can be unsubscribed to cancel execution
- Rich Operators: Extensive operator library for transformation
Example
// Observable Example (using RxJS)
const { Observable } = require("rxjs");
const observableExample = () => {
const observable = new Observable((subscriber) => {
let count = 0;
const intervalId = setInterval(() => {
subscriber.next(`Observable value: ${count++}`);
}, 1000);
// Cleanup function
return () => {
clearInterval(intervalId);
console.log("Observable cleaned up");
};
});
const subscription = observable.subscribe(console.log);
// Unsubscribe after 5 seconds
setTimeout(() => {
subscription.unsubscribe();
console.log("Observable unsubscribed");
}, 5000);
};
observableExample();
Detailed Comparison
1. Value Emission
Promises: Emit a single value
const promise = new Promise((resolve) => {
resolve("Single value");
});
promise.then((value) => console.log(value)); // "Single value"
// That's it - promise is done
Observables: Can emit multiple values
const observable = new Observable((subscriber) => {
subscriber.next("First value");
subscriber.next("Second value");
subscriber.next("Third value");
subscriber.complete();
});
observable.subscribe((value) => console.log(value));
// "First value"
// "Second value"
// "Third value"
2. Execution Timing
Promises: Execute immediately
console.log("Before promise");
const promise = new Promise((resolve) => {
console.log("Promise executing"); // Runs immediately
resolve("Done");
});
console.log("After promise");
// Output:
// "Before promise"
// "Promise executing"
// "After promise"
Observables: Lazy execution (only when subscribed)
console.log("Before observable");
const observable = new Observable((subscriber) => {
console.log("Observable executing"); // Doesn't run yet
subscriber.next("Value");
});
console.log("After observable");
// Output:
// "Before observable"
// "After observable"
// (Nothing happens until subscription)
observable.subscribe(); // Now it executes
// "Observable executing"
3. Cancellation
Promises: Cannot be cancelled
const promise = fetch("/api/data");
// Once started, cannot stop it
promise.then((data) => console.log(data));
// No way to cancel if user navigates away
Observables: Can be cancelled
const observable = new Observable((subscriber) => {
const intervalId = setInterval(() => {
subscriber.next("Tick");
}, 1000);
return () => clearInterval(intervalId); // Cleanup
});
const subscription = observable.subscribe(console.log);
// Later, cancel it
subscription.unsubscribe(); // Stops execution and cleans up
4. Transformation Capabilities
Promises: Basic chaining
fetch("/api/data")
.then((response) => response.json())
.then((data) => data.filter((item) => item.active))
.then((activeItems) => activeItems.map((item) => item.name))
.catch((error) => console.error(error));
Observables: Rich operators
import { from, map, filter, debounceTime, mergeMap } from "rxjs";
from(fetch("/api/data"))
.pipe(
mergeMap((response) => response.json()),
filter((item) => item.active),
debounceTime(300),
map((item) => item.name)
)
.subscribe(console.log);
Use Cases
When to Use Promises
✅ One-time async operations
- API calls that return a single response
- File reading/writing
- Database queries
- Simple async/await scenarios
// Perfect for Promises
async function getUserData(userId) {
const user = await fetch(`/api/users/${userId}`).then((r) => r.json());
const posts = await fetch(`/api/posts/${userId}`).then((r) => r.json());
return { user, posts };
}
When to Use Observables
✅ Streams of data
- Real-time data (WebSocket connections)
- User input events (keyboard, mouse)
- Time-based sequences (intervals, timers)
- Multiple values over time
- Complex event handling
// Perfect for Observables
import { fromEvent, debounceTime, map } from "rxjs";
const searchInput = document.getElementById("search");
fromEvent(searchInput, "input")
.pipe(
debounceTime(300),
map((event) => event.target.value),
mergeMap((query) => fetch(`/api/search?q=${query}`))
)
.subscribe((results) => displayResults(results));
Converting Between Promises and Observables
Promise to Observable
import { from } from "rxjs";
const promise = fetch("/api/data").then((r) => r.json());
const observable = from(promise);
observable.subscribe((data) => console.log(data));
Observable to Promise
import { firstValueFrom } from "rxjs";
const observable = new Observable((subscriber) => {
subscriber.next("value");
subscriber.complete();
});
const promise = firstValueFrom(observable);
promise.then((value) => console.log(value));
Why Angular Uses Observables
Angular heavily uses Observables (via RxJS) because:
- HTTP Client: Returns Observables for HTTP requests
- Reactive Forms: Form value changes are Observables
- Router Events: Navigation events are Observables
- Real-time Updates: Perfect for WebSocket connections
- Event Handling: User interactions can be treated as streams
// Angular example
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Component({...})
export class UserComponent {
constructor(private http: HttpClient) {}
// HTTP returns Observable
users$ = this.http.get('/api/users');
// Can chain operators
activeUsers$ = this.users$.pipe(
map(users => users.filter(u => u.active))
);
}
Common Observable Operators
import { map, filter, debounceTime, mergeMap, switchMap, take } from "rxjs";
observable
.pipe(
map((x) => x * 2), // Transform values
filter((x) => x > 10), // Filter values
debounceTime(300), // Wait for pause
mergeMap((x) => fetch(x)), // Flatten inner observables
take(5) // Take first 5 values
)
.subscribe(console.log);
Error Handling
Promises
promise
.then((data) => process(data))
.catch((error) => {
console.error(error);
// Promise chain ends here
});
Observables
observable
.pipe(
map((data) => process(data)),
catchError((error) => {
console.error(error);
return of(defaultValue); // Can continue with default value
})
)
.subscribe(console.log);
Summary
- Promises: Best for single async operations, simple error handling, and when you need immediate execution
- Observables: Best for streams of data, real-time updates, complex transformations, and when you need cancellation
Choose Promises for simple, one-time async operations. Choose Observables for reactive programming, event streams, and complex data transformations. Many modern frameworks (like Angular) use Observables extensively, while others (like React) primarily use Promises with async/await.