Empty Vs Never#
Let's clarify the difference between the RxJS constants EMPTY
and NEVER
. Both are simple, pre-defined Observable constants, but they represent very different stream behaviors, primarily concerning completion.
Think of it like this: Both represent a stream that will never give you any data (no next
emissions). The difference lies in whether they tell you they are finished or just stay silent forever.
-
EMPTY
- What it does: Represents an Observable that emits zero items.
- Key Behavior: As soon as you subscribe to it, it immediately sends a
complete
notification. - Analogy: It's like a function that returns immediately without doing anything (
return;
), or reading an empty file. It quickly signals "I have nothing to give you, and I'm done." - Use Case: Useful when you need an Observable that does nothing but signal successful completion right away. This is often helpful in conditional logic within higher-order mapping operators (like
switchMap
,mergeMap
,concatMap
). For example, if a condition isn't met, you might returnEMPTY
instead of making an API call, indicating that the operation for that specific trigger completed successfully without producing a value.
-
NEVER
- What it does: Represents an Observable that emits zero items.
- Key Behavior: It never sends a
complete
notification and never sends anerror
notification. It remains silent indefinitely after subscription. - Analogy: It's like a process that hangs forever without producing output or terminating, or a phone line that just keeps ringing and ringing without ever being answered or going to an error state. It signals "I have nothing for you right now, and I might never have anything, and I'm certainly not finished."
- Use Case: Represents a stream that simply never emits or terminates. This can be useful in testing scenarios or when you want to keep a combination operator (like
race
orcombineLatest
) alive, even if one of its potential sources will never produce a relevant value or complete. It effectively keeps that "slot" open indefinitely without signalling completion or error. It can also be used intentionally to prevent parts of an Observable chain from completing.
Direct Comparison#
Feature | EMPTY |
NEVER |
---|---|---|
next emissions |
0 | 0 |
complete |
Yes (immediately) | No (never) |
error |
No (by default) | No (never) |
Terminates? | Yes (completes immediately) | No (runs indefinitely) |
Code Snippet Demonstration#
import { Component, OnInit, OnDestroy } from "@angular/core";
import { EMPTY, NEVER, Subscription } from "rxjs";
@Component({
selector: "app-empty-never-demo",
template: `
<h4>EMPTY vs NEVER Demo</h4>
<p>
Check the console log at ${new Date().toLocaleTimeString("en-IN", {
timeZone: "Asia/Kolkata",
})} (IST).
</p>
<p>EMPTY Status: {{ emptyStatus }}</p>
<p>NEVER Status: {{ neverStatus }}</p>
`,
})
export class EmptyNeverDemoComponent implements OnInit, OnDestroy {
emptyStatus = "Subscribing...";
neverStatus = "Subscribing...";
private emptySub: Subscription | undefined;
private neverSub: Subscription | undefined;
ngOnInit(): void {
console.log(`--- Subscribing to EMPTY ---`);
this.emptySub = EMPTY.subscribe({
next: () => {
console.log("EMPTY: next (This will not be logged)");
this.emptyStatus = "Got next (unexpected)";
},
error: (err) => {
console.error("EMPTY: error", err);
this.emptyStatus = `Error: ${err}`;
},
complete: () => {
// This is called immediately!
console.log(
`EMPTY: complete! (Called immediately) at ${new Date().toLocaleTimeString()}`
);
this.emptyStatus = "Completed immediately";
},
});
// The line below will likely log 'Completed immediately' because EMPTY completes synchronously
console.log(
`Current EMPTY status after sync subscribe: ${this.emptyStatus}`
);
console.log(`\n--- Subscribing to NEVER ---`);
this.neverSub = NEVER.subscribe({
next: () => {
console.log("NEVER: next (This will not be logged)");
this.neverStatus = "Got next (unexpected)";
},
error: (err) => {
console.error("NEVER: error (This will not be logged)");
this.neverStatus = `Error: ${err}`;
},
complete: () => {
// This is never called!
console.log("NEVER: complete! (This will never be logged)");
this.neverStatus = "Completed (unexpected)";
},
});
// NEVER does nothing, so status remains 'Subscribing...'
console.log(
`Current NEVER status after sync subscribe: ${this.neverStatus}`
);
// Set a timeout just to show NEVER doesn't complete on its own
setTimeout(() => {
if (this.neverStatus === "Subscribing...") {
console.log(
"\nAfter 2 seconds, NEVER still hasn't emitted or completed."
);
this.neverStatus = "Still running after 2s (as expected)";
}
}, 2000);
}
ngOnDestroy(): void {
console.log("\n--- Component Destroying ---");
if (this.emptySub && !this.emptySub.closed) {
// This check is usually false as EMPTY completes immediately
console.log("Unsubscribing from EMPTY (already closed likely).");
this.emptySub.unsubscribe();
} else {
console.log("EMPTY subscription was already closed.");
}
if (this.neverSub && !this.neverSub.closed) {
// This is important for NEVER if the component is destroyed
console.log("Unsubscribing from NEVER.");
this.neverSub.unsubscribe();
}
}
}
Summary#
Choose EMPTY
when you need an Observable that does nothing but signals successful completion instantly.
Choose NEVER
when you need an Observable that does nothing and never signals completion or error.