Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

combineLatest([]) behaves like EMPTY #7514

Closed
defaultbranch opened this issue Nov 8, 2024 · 3 comments
Closed

combineLatest([]) behaves like EMPTY #7514

defaultbranch opened this issue Nov 8, 2024 · 3 comments

Comments

@defaultbranch
Copy link

defaultbranch commented Nov 8, 2024

Describe the bug

combineLatest will not emit a value before all, and at least one, of its observables have emitted a value, thus:

combineLatest([]) = NEVER EMPTY

-- the at least one seems wrong to me

Expected behavior

combineLatest will not emit a value before none of its observables has not emitted a value yet, thus:

combineLatest([]) = of([])

Workaround: check the array length upfront (typescript):

const workaround
  : (observables: Array<Observable<unknown>>) => Observable<Array<unknown>>
  = (observables) => observables.length > 0 ? combineLatest(observables) : of([]);

Reproduction code

import { combineLatest, of } from 'rxjs';

combineLatest([of(1), of(2)]).subscribe(it => console.log('if input is an array of two, output is an array of two', it));
combineLatest([of(1)]).subscribe(it => console.log('if input is an array of one, output is an array of one', it));
combineLatest([]).subscribe(it => console.log('if input is an empty array, output is an empty array'));

// Expected output:
//  if input is an array of two, output is an array of two ▶ [1, 2]
//  if input is an array of one, output is an array of one ▶ [1]
//  if input is an empty array, output is an empty array ▶ []
//  <EOF>

// Actual output:
//  if input is an array of two, output is an array of two ▶ [1, 2]
//  if input is an array of one, output is an array of one ▶ [1]
//  <EOF>

Reproduction URL

No response

Version

7.8.1

Environment

No response

Additional context

I use combineLatest to map an array of observable BehaviorSubjects to an observable array. Because BehaviorSubject always has a value to return, combineLatest will always deliver a value when the array is non-empty. But as I found out, it will never deliver a value when the array is empty, possibly blocking a code that was not expecting this behavior.

@demensky
Copy link
Contributor

demensky commented Nov 8, 2024

combineLatest([]) is not the same as NEVER, it's more like EMPTY.

combineLatest([]).subscribe({
  next: (value) => console.log('combineLatest next', value),
  complete: () => console.log('combineLatest complete'),
});

EMPTY.subscribe({
  next: (value) => console.log('EMPTY next', value),
  complete: () => console.log('EMPTY complete'),
});

NEVER.subscribe({
  next: (value) => console.log('NEVER next', value),
  complete: () => console.log('NEVER complete'),
});
combineLatest complete
EMPTY complete

As for me, this behavior is completely logical.

Following your logic, what should happen in this case?

combineLatest([EMPTY, of(2)]).subscribe((it) => {
  console.log('result', it);
});

I can offer this solution:

combineLatest([])
  .pipe(defaultIfEmpty([]))
  .subscribe((values) => {
    console.log('result', values);
  });

@defaultbranch defaultbranch changed the title combineLatest([]) behaves like NEVER combineLatest([]) behaves like EMPTY Nov 8, 2024
@defaultbranch
Copy link
Author

Thanks for the feedback; I was actually looking forward to a little discussion to see whether my expectation makes sense or whether it conflicts with other constraints. I have adjusted the title.

Regarding for combineLatest([EMPTY, of(2)]), since this is not an empty array but one of the observables completes immediately, it's ok that the overall also completes immediately; I don't disagree with that.

My use case is that I need to "flatten" an Array<Observable<any>> to Observable<Array<any>>, and combineLatest seems to be cut out for exactly that task, only when the array is empty, its behavior is off.

Building on your use case, I agree my expectation comes out strange:

import { combineLatest, of } from 'rxjs';

combineLatest([]).subscribe({
  next: (value) => console.log('combineLatest next', value),
  complete: () => console.log('combineLatest complete'),
});

// Output expected according to my use case:
//  combineLatest next ▶ []
//  combineLatest complete
//  <EOF>

// Actual output:
//  combineLatest complete
//  <EOF>

It's like limit value consideration from two sides 😄

@defaultbranch
Copy link
Author

defaultbranch commented Nov 8, 2024

Probably best to leave combineLatest at the established behavior.

I guess I can just build my own collect method to map Array<Observable> to Observable<Array> using defaultIfEmpty (typescript):

import { Observable, combineLatest, of, defaultIfEmpty } from 'rxjs';

type UnwrapObservable<T> = T extends Observable<infer U> ? U : never;
type ConvertToObservableTuple<T extends readonly any[]> = Observable<{ [K in keyof T]: UnwrapObservable<T[K]> }>;

const collect
  : <T extends readonly any[]>(observables: T) => ConvertToObservableTuple<T>
  = <T extends readonly any[]>(observables: T) => combineLatest(observables).pipe(defaultIfEmpty([])) as ConvertToObservableTuple<T>;

const x$: Observable<[number, string]> = collect([ of(1), of('a') ]);
const y$: Observable<[]> = collect([]);

@defaultbranch defaultbranch reopened this Nov 8, 2024
@defaultbranch defaultbranch closed this as not planned Won't fix, can't repro, duplicate, stale Nov 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants