rxjs: observeOn with asapScheduler and asyncScheduler: both are executed simultanously

Viewed 1646

I am currently learning about RXJS and its schedulers. In a first example, I tried an example of a course like following (I am aware that the of()-operator in this example is deprecated):

console.log('start');

let queue = of('QueueScheduler (synchronous)', queueScheduler);
let asap = of('AsapScheduler (async micro task)', asapScheduler);
let async = of('AsyncScheduler (async task)', asyncScheduler);
merge(async, asap, queue).subscribe(val => console.log(val));

console.log('end');

which results in following output:

start
QueueScheduler (synchronous)
end
AsapScheduler (async micro task) 
AsyncScheduler (async task)

This is as expected, since in my understanding, the order of processing is Sync Task Queue -> Async MicroTask Queue -> Async Task Queue.

However, in a second example using the observeOn()-operator, I don't understand the logs. Here is the example:

console.log('start');

from([1, 2], queueScheduler)
  .pipe(
    tap(val => console.log(`value ${val}`)),
    observeOn(asyncScheduler),
    tap(val => console.log(`double ${val * 2}`)),
    observeOn(asapScheduler),
    tap(val => console.log(`triple ${val * 3}`))
  )
  .subscribe();

console.log('end');

In this case, following output gets generated:

start
value 1
value 2
end
double 2
triple 3
double 4
triple 6

Why does the second and third tap get executet simultanously? Is the second observeOn()-operator overriding the first one? In my understanding of the queues, the output should be

start
value 1
value 2
end
triple 3
triple 6
double 2
double 4

because the second tap()-operator is pushed into the asyncScheduler, while the third one is pushed in the asapScheduler, which should execute before the asyncScheduler. What am I missing here?

2 Answers

As you know about observeOn() :

It might be useful, if you do not have control over internal scheduler of a given Observable, but want to control when its values are emitted nevertheless.

Lets see what schedulers we have and what is it's job

No Scheduler :

//value will be emitted at once — synchronously

of(1).subscribe(console.log)

queueScheduler :

//value will be emitted at once — synchronously

of(1, queueScheduler).subscribe(console.log)

asapScheduler :

// value will be emitted in mIcrotask just after current mAcrotask
of(1, asapScheduler).subscribe(console.log)

asyncScheduler :

// value will be emitted in another mAcrotask
of(1, asyncScheduler).subscribe(console.log)

animationFrameScheduler :

// value will be emitted in another mAcrotask just before browser repaint
of(1, animationFrameScheduler).subscribe(console.log)

try this example to see the difference between asapScheduler and asynScheduler

of('obs 1.1', 'obs 1.2', 'obs 1.3')
  .pipe(observeOn(asapScheduler))
  .subscribe(console.log);
alert('=======');
of('obs 2.1', 'obs 2.2', 'obs 2.3')
  .pipe(observeOn(asyncScheduler))
  .subscribe(console.log);
of('obs 3.1', 'obs 3.2', 'obs 3.3')
  .pipe()
  .subscribe(console.log);

it will logs

obs 3.1
obs 3.2
obs 3.3
obs 1.1
obs 1.2
obs 1.3
obs 2.1
obs 2.2
obs 2.2

What you are missing is that data flows through the pipe operators in order, regardless they are async or not.

So in your example, both "1" and "2" reach the first tap Synchronously, and so console shows:

start
value 1
value 2
end

Then the asyncScheduler will queue the "1" on the next macrotask. When that macrotask runs the next tap operator is executed, so you see:

double 2

After that two things happend: the "1" is queued by the asapScheduler (next microtask), and the "2" is queued by the asyncScheduler (next macrotask). As you know, microtask will fire earlier, so you see:

triple 3

Finally the next macrotask is executed, which will log the double 4 and queue the "2" on the next microtask, so you see:

double 4
triple 6
Related