3

I've created two functions for testing Observables that each return an Observable:

foo() {
  return new Observable(observer => {
    let i = 0;
    setInterval( () => {
      if(i === 10) {
        observer.complete();
      } else {
        observer.next(i);
        i++;
      }
    }, 1000);
    // If I call observer.complete() here then it never completes
  });
}

bar(fooResult) {
  return new Observable(observer => {
    let j = 0;
    setInterval( () => {
      if(fooResult) {
        observer.next('j -> '+j+' plus fooResult '+JSON.stringify(fooResult));
        observer.complete();
      } else {
        observer.next(j);
        j++;
      }
    }, 2000);
  });
}

And make use of them like this:

    let fooResult = [];

    // Testing observables...
    this.exampleProductService.foo().subscribe(
      (res) => { 
        console.log('foo next() -> '+res);
        fooResult.push(res);
      },
      (err) => {
        console.error('foo error: '+JSON.stringify(err));
      },
      () => { 
        console.log('foo finished');
        this.exampleProductService.bar(fooResult).subscribe(
          (res) => {
            console.log('bar next() -> '+res);
          },
           (err) => {
            console.error('bar error: '+JSON.stringify(err));
          },
          () => {
            console.log('bar finished');
          }
        );
      }
    );

Make question(s) are:

  1. Is there a better way to pass data from the completion of an Observable to another function that also returns an Observable? Building up an array seems cumbersome and I can't do the following as the complete callback part of the Observable doesn't pass a parameter like progressUpdate and onError:

    (complete) => { this.exampleProductService.bar(complete).// rest of code } 
    
  2. I tried assigning the result of the first function to a variable and then passing that variable but, as expected, I got an Observable and not the result I wanted.

  3. Is there anything incorrect about how I'm doing the above?

Thanks

P.S. This is an Angular 2 application!

Katana24
  • 8,706
  • 19
  • 76
  • 118

2 Answers2

4

I think your functions are a little overly complex. For one, don't use the constructor when there are factory functions already available, in this case interval or timer as @Meir pointed out, though in this case it would be more verbose.

Second, the bar function doesn't really make much sense as it stands, since you seem to be waiting for something to complete which you already know has completed (since you don't subscribe to it until the completion block of the Observable generated by foo).

I refactored according to your stated goal of waiting for one Observable to complete before starting a second one, while using the results of the first in the second.

// Factory function to emit 10 items, 1 every second
function foo() {
  return Observable.interval(1000)
    .take(10);
}

// Lifts the passed in value into an Observable and stringfys it
function bar(fooResult) {
  return Rx.Observable.of(fooResult)
    .map(res => JSON.stringify(fooResult))
}

Now when using them you would do instead:

foo()
  // Log the side effects of foo
  .do(
    x => console.log(`foo next() -> ${x}`),
   err => console.error(`foo error: ${JSON.stringify(err)}`),
   () => console.log('foo finished')
  )
  // Collect the results from foo and only emit when foo completes
  .reduce((total, diff) => [...total, diff], [])

  // Pass the result from the reduce on to bar
  .concatMap(fooResult => bar(fooResult))

  //Subscribe to the results of bar
  .subscribe(
    res => console.log(`bar next() -> ${res}`),
    err => console.error(`bar error: ${JSON.stringify(err)}`),
    ()  => console.log('bar finished')
  );

Notice above I am also getting rid of global state, which is the anathema of functional programming. Where ever possible your state should be localized to the stream.

See the working example here: http://jsbin.com/pexayohoho/1/edit?js,console

paulpdaniels
  • 18,395
  • 2
  • 51
  • 55
  • Thanks for the feedbacl paul. How specifically are you getting rid of global state in this instance? – Katana24 Oct 05 '16 at 12:05
  • Also - can you explain this line a bit more please? .reduce((total, diff) => [...total, diff], []) From the documentation this acts as an accumulator on the Observable and returns one value when i completes. I understand that but the optional seed supplied (the diff) what is that and what does the [...total, diff] mean - I have never seen this before – Katana24 Oct 05 '16 at 12:11
  • 1
    re: global state, there is no longer a global array called `fooResult`. re: syntax, `[...total, diff]` is ES6 short hand for `total.concat([diff])`, basically it is creating a new array copy plus the new item to avoid mutating the existing one. `diff` is the new value that is coming in, not the optional seed, that is supplied as an empty array `[]`. Does that answer your question? – paulpdaniels Oct 05 '16 at 17:55
  • Yes paulpdaniels that does. Thanks for the clarification – Katana24 Oct 07 '16 at 08:50
1

I'm not sure what you're trying to achieve, but here is a jsbin that attempts to duplicate your code.

Few things to note:

your foo() is much easier to create using Observable.timer and .take() operator.

your bar() can be used by another timer with a map and a .takeWhile() operator.

As for the last subscription (the complete part), it would only print 'foo finished' but nothing else, because it subscribes to a non buffered sequence that was already terminated.

Meir
  • 14,081
  • 4
  • 39
  • 47
  • Thanks for the feedback Meir - what I was trying to achieve was a way to pass the output from one observable to another. I like the brevity of the foo part in your example – Katana24 Oct 05 '16 at 12:02