Remi
Remi

Reputation: 5367

Throw error when an Observable hasn't been set in combineLatest

In the application combineLatest is used to combine three observables:

class SomeComponent {
  private heightProvider = new SubjectProvider<any>(this);
  private marginsProvider = new SubjectProvider<any>(this);
  private domainProvider = new SubjectProvider<any>(this);

  arbitraryMethod(): void {
    combineLatest([
      this.heightProvider.value$,
      this.marginsProvider.value$,
      this.domainProvider.value$
    ]).pipe(
      map(([height, margins, domain]) => {
        // ...
      }
  }
  

  setHeight(height: number): void {
    this.heightProvider.next(height);
  }

  setMargins(margins: {}): void {
    this.marginsProvider.next(margins);
  }

  setDomain(domain: []): void {
    this.domainProvider.next(domain);
  }
}

However, I've noticed a few times already that I am sometimes forgetting to set one of these observables.

Is there a way I can build in error handeling that throws to console once one of these isn't set?

Upvotes: 0

Views: 152

Answers (1)

Mrk Sef
Mrk Sef

Reputation: 8052

Observables aren't typically 'set' or 'not set'. I'm not sure what you mean by this. If you have a predicate that can check your observables, here is how you might use it.

// predicate
function notSet(o: Observable<any>): Boolean{
  //...
}

scale$: Observable<any> = defer(() => {

  const combining = [
    this.heightProvider.value$,
    this.marginsProvider.value$,
    this.domainProvider.value$
  ];

  const allSet = !combining.find(notSet)

  if(!allSet) console.log("Not Set Error");

  return !allSet?
    EMPTY :
    combineLatest(combining).pipe(
      map(([height, margins, domain]) => {
      // ...
}

Update

Ensursing source observables have emitted

If I understand your problem properly, you want to throw an error if any of your source observables haven't emitted yet. At its heart, this feels like a simple problem, but it happens to be a problem for which there doesn't exist a single general solution.

Your solution has to be domain-specific to some extent.

A simplified example of a similar problem

What you're asking a similar to this:

  • How do I throw an error if 'add' isn't invoked with a second number?
const add = (a: number) => (b: number): number => {
  // How do I throw an error if this function 
  // isn't invoked with a second number?
  return a + b;
}

/***********
 * Example 1
 ***********/
// add is being called with one number
const add5 = add(5);
...
/* More code here */
...
// add is being called with a second number
const result = add5(50); 
console.log(result); // Prints "55"

/***********
 * Example 2
 ***********/
const result = add(5)(20); // Add is being called with both numbers
console.log(result); // Prints "55"

/***********
 * Example 3
 ***********/
// add is being called with one number
const add5 = add(5);
...
/* More code here */
...
// add was never given a second number
return 
// Add throws an error? How?

How can you write add such that it throws an error if the second number isn't 'set'? Well, there's no simple answer. add doesn't know the future and can't guess whether that second number was forgotten or will still be set in the future. To add, those two scenarios look the same.

One solution is to re-write add so that it must take both parameters at once. If either is missing, throw an error:

const add = (a: number, b: number): number => {
  if(a != null && b != null){
    return a + b;
  }
  throw "add: invalid argument error";
}

This solution fundamentally changes how add works. This solution doesn't work if I have a requirement that add must take its arguments one at a time.

If I want add to keep that behaviour, perhaps I can set a timer and throw an error if the second argument isn't given fast enough.

const add = (a: number) => {
  const t = setTimeout(
    () => throw "add: argument timeout error"), 
    1000 // wait 1 second
  );
  return (b: number): number => {
    clearTimeout(t); // cancel the error
    return a + b;
  }
}

Now add takes its arguments one at a time, but is a timeout really how I want this to work? Maybe I only care that add is given a second parameter before some other event (an API call returns or a user navigates away from the page) or something.

Hopefully, you can begin to understand how such a "simple" problem has only domain-specific solutions.


Observables

Your question, as writ, doesn't tell us enough about what you're trying to accomplish to guess what behaviour you want.

Observables have a lot of power built into them to allow you to design a solution specific to your needs. It's almost certain that you can throw an error if one of your observables isn't set, but first, you must define what this even means.

Is it not set quickly enough? Is it not set in time for a certain function call? Not set when an event is raised? Never set? How would you like to define never? When the program is shut down?

Maybe you could switch your Subjects for BehaviourSubjects so that they MUST always have a value set (sort of like add taking both arguments at once instead of one at a time).

All of these things (and many many many more) are possible.

Upvotes: 1

Related Questions