loading...
Cover image for RxJS Autorun Intro
RxJS

RxJS Autorun Intro

kosich profile image Kostia Palchyk ・Updated on ・3 min read

Hey, RxJS streamers! 🙋‍♂️

Today we're going to review a small library that re-evaluates an expression based on updates in streams it uses.

tl;dr: docs and package at github.com/kosich/rxjs-autorun 🔗

Let's boldly explore it!

dog diving in snow

It's a sweetened countdown

Our first example.

Say, we want to prettify each value on a timer stream. So we'll write such an expression:

import { timer } from 'rxjs';
import { computed, $ } from 'rxjs-autorun';

// timer would emit every second
const a = timer(0, 1_000);
// expression would concat every emission with a cake
const result = computed(() => $(a) + ' 🍰');
// now we should subscribe to the resulting stream
result.subscribe(x => console.log(x));
// > 0 🍰
// > 1 🍰
// > 2 🍰
// > …
Enter fullscreen mode Exit fullscreen mode

Explanation: computed takes a function that uses some streams and re-evaluates it when those streams update. It returns an observable that you can manipulate further. And $(a) indicates that a is a stream and its updates should be listened to.

So technically this expression is equivalent to a.pipe( map(x => x + '🍰') )

But let's keep discovering what else this tiny lib can do:

Infinite monkey theorem needs infinite bananas

Here we'll combine a timer that would represent a queue of our little 🐒 fellas with a stream of fetched bananas 🍌:

import { timer, of } from 'rxjs';
import { delay } from 'rxjs/operators';
import { computed, $ } from 'rxjs-autorun';

const a = timer(0, 1_000); // get some monkeys
const b = of('🍌').pipe(delay(2_000)); // fetch some treats
const result = computed(() => '🐒 #' + $(a) + ' gets ' + $(b)); // mix
result.subscribe(x => console.log(x)); // listen to result stream
// ...2s gap...
// > 🐒 #1 gets 🍌
// > 🐒 #2 gets 🍌
// > 🐒 #3 gets 🍌
// > …
Enter fullscreen mode Exit fullscreen mode
BTW, did you know that banana is technically a berry?

Not hard at all, is it?

This expression is similar to combineLatest(a, b).pipe( map(([x,y]) => x + y) ).

Let's review another multiple streams example:

Who's up to some pizza?

The last trick we're gonna learn today is ability to read latest values without tracking their updates:

import { Subject } from 'rxjs';
import { computed, $, _ } from 'rxjs-autorun';

const a = new Subject(); // neighbours
const b = new Subject(); // food
computed(() => $(a) + ' likes ' + _(b))
  .subscribe(x => console.log(x));
a.next('🐈'); // nothing: b is still empty
b.next('🥛'); // > 🐈 likes 🥛
a.next('🐭'); // > 🐭 likes 🥛
b.next('🍕'); // nothing: _(b) doesn't trigger re-runs
a.next('🙋‍♂️'); // 🙋‍♂️ likes 🍕
Enter fullscreen mode Exit fullscreen mode

Explanation: _ function indicates that we need to take one value from a stream, but we don't want to recalculate our expression when this particular stream emits. So if an expression uses $(a) and _(b) — it would react only to a updates.

This also means that computed(() => _(a)) expression would emit one value and immediately complete.

Okay, one really last thing before we wrap-up:

Transformation

This time, try to guess what it is:

import { timer, of } from 'rxjs';
import { computed, $, _ } from 'rxjs-autorun';

const a = timer(0, 1_000);
const b = of('💧');
const c = of('❄');
const result = computed(() => $(a) % 2 ? _(b) : _(c));
result.subscribe(x => console.log(x));
Enter fullscreen mode Exit fullscreen mode

Indeed, this is capricious weather 🙂

Actually, this expression is somewhat similar to switchMap

Outro 😳

All the examples you can try here.

And there's more to the library, go explore it yourself!

In the following articles we'll review how to filter emissions and how to manage subscriptions inside rxjs-autorun expressions. Not to miss those and other RxJS posts — follow me here and on twitter!

If you enjoyed reading — please, indicate that with ❤️ 🦄 📘 buttons — it helps a lot!

Thank you for reading this article! Stay reactive and have a nice day 🙂

Also I want to thank @fkrasnowski for lengthy discussions of this idea, @ryansolid for giving it a try and Johan for collaborating with me on this! 🙏

Psst.. need something more to read?

I got you covered:

😉

Discussion

pic
Editor guide
Collapse
tohagan profile image
Tony OHagan

Looks the same as Vue’s computed functions. In Vue, I often setup a $now observable to compute and display a timestamp age.

Collapse
kosich profile image
Kostia Palchyk Author

Ah, yes, it is similar to Vue's computed! It was one of the inspirations for this lib and we had a hope it would look familiar to Vue devs!

And with Observable streams you get additional features. For example, if a value is not produced synchronously — autorun would halt execution until it does. Exploiting this feature we can track never emitting stream to get a filtering behavior:

const a = timer(0, 1_000);
const c = computed(() => $(a) % 2 ? $(NEVER) : $(a) + " - even");
// > 0 - even > 2 - even > …
Enter fullscreen mode Exit fullscreen mode

NOTE: it's not undefined at 1, it's skipped!

↑ try this @ stackblitz.com/edit/rxjs-autorun-f...

Not to mention that you can pipe the resulting stream with the many Rx operators. And I wouldn't even start describing late subscription / early unsubscription flows — the subject is too big for a comment 🙂