question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[feat] `rxjs/concat` : pass last value of previous Observable to the next one

See original GitHub issue

Problem

The “master” subscriber needs values emitted by outer observable “as they happen”; and when it completes, should seamlessly switch over to the next observable in the chain. (the next in the chain also needs the last emitted value of prev/outer obs to compute its own result)

Tried and failed

I tried using concat from rxjs but that doesn’t pass the last value emitted by previous observable to the next one in chain.

I tried using concatMap from rxjs/operators but that “holds up” the emission until outer and inner observables are done. (I reckon its only good for preserving the order)

Solution

type ObservableGenerator<T> = Observable<T> | ((v: T) => Observable<T>)

function chain<T>(seed: Observable<T>, ...obsGenerators: Array<ObservableGenerator<T>>) {
  return new Observable<T>(masterSubscriber => {
    let lastValueFromPrev: T
    let childSubscription: Subscription

    const switchTo = (obs: Observable<T>) => {
      childSubscription = obs.subscribe({
        next: v => {
          lastValueFromPrev = v
          masterSubscriber.next(v)
        },
        error: masterSubscriber.error,
        complete: () => {
          childSubscription.unsubscribe()
          const next = obsGenerators.shift()
          if(!next) {
            masterSubscriber.complete()
          } else {
            switchTo(typeof next === 'function' ? next(lastValueFromPrev) : next)
          }
        }
      })
    }

    switchTo(seed)

    return () => {
      childSubscription.unsubscribe()
    }
  })
}

And then it can be used like so:

EDIT: Example removed.

Much better example below: https://github.com/ReactiveX/rxjs/issues/4711#issuecomment-483080247

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:10 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
zikaaricommented, Apr 15, 2019

Here’s a much better example:

Imagine we have an asynchronous image rendering pipeline. There are multiple filters/shaders in the said pipeline and each of them takes ImageData from the preceding filter/shader, transforms it, and passes it on to the next one.

Here’s the tricky part; the user must see the process as it happens; live. Instead of just the final output. That way he can cancel the render midway if he notices some rendering artifacts during the render.

Each filter/shader can only process 128 pixel block at a time. Every requestAnimationFrame cycle it’ll .next(...) what it has computed so far, and then on to the next 128 pixel block. Accumulate, emit and repeat. .complete() when it is done.

Here’s what it’ll look like:


// each of the filters below will compute 128x128px block at a time
// computed block is "reduced" with existing ImageData
// upon every `requestAnimationFrame` cycle, filters will `.next` what they have

const removeHighlights: Observable<ImageData> = (imageData: ImageData) => { /* impl */ }
const removeShadows: Observable<ImageData> = (imageData: ImageData) => { /* impl */ }
const removeGlare: Observable<ImageData> = (imageData: ImageData) => { /* impl */ }
const applyGammaShift: Observable<ImageData> = (imageData: ImageData) => { /* impl */ }
const applyAntiAliasing: Observable<ImageData> = (imageData: ImageData) => { /* impl */ }
const applyPostFx: Observable<ImageData> = (imageData: ImageData) => { /* impl */ }

// here each of filters are run one at a time
// `removeGlare` won't be run until `removeShadows` is `.complete()`
// each filter won't be `.complete()` until it has processed all of the image
// also each filter builds on top of preceding filter's work 
this.imageData$ = chain(
	this.imageInput$.pipe(map(this.convertToImageData)),
    removeHighlights,
    removeShadows,
    removeGlare,
    applyGammaShift,
    applyAntiAliasing,
    applyPostFx
)

// called every time each filter process a 128x128 block (live preview that is)
this.imageData$.subscribe(this.ctx.drawImage)

// PS: I know this isn't the most efficient way to do this. This is just to make a point.

Illustration above is not same as:

this.imageData$ = this.imageInput$.pipe(
  // all the filters here
)

Here imageData$ won’t receive anything until entire pipeline is .complete(). We want live output.

0reactions
zikaaricommented, Apr 15, 2019

Makes sense. Thanks for the explanation.

Read more comments on GitHub >

github_iconTop Results From Across the Web

RxJS - concat last emitted value with previous values
The concat operator subscribes to the second Observable b$ only after $a completed. However at the time $a completes the value 3 was...
Read more >
concat - RxJS Reference | indepth.dev
When a new value arrives from a source observable, pass it down to an observer; Once this source observable completes, subscribe to the...
Read more >
RxJS - concat last emitted value with previous values-rx.js
The concat operator subscribes to the second Observable b$ only after $a completed. However at the time $a completes the value 3 was...
Read more >
concat - RxJS
concat<T, A extends readonly, unknown>(). Parameters. There are no parameters. Code licensed under an Apache-2.0 License. Documentation licensed under CC BY ...
Read more >
Concat operator - ReactiveX
The Concat operator concatenates the output of multiple Observables so that they ... Observable that you pass to it until the previous Observable...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found