What's the point of AsyncSubject in RXJS?
Asked Answered
R

4

17

The documentation for RxJS defines AsyncSubject as follows:

The AsyncSubject is a variant where only the last value of the Observable execution is sent to its observers, and only when the execution completes.

I don't see where / why I would ever need to use this variant of subject. Can someone provide an explanation or a real-world example to illustrate why it exists and its advantages?

Rodie answered 17/1, 2018 at 17:34 Comment(5)
AsyncSubject exists to facilitate the implementation of publishLast. Perhaps it's easier to see a use case for that?Ingles
I was going to write an answer to this question, but I wrote this article, instead.Ingles
@Ingles That's going above and beyond alright! But for it to be an answer I can accept, it needs to be answer with the content of the blog post within it. Still thanks!Rodie
Who names these things! :-/Karilynn
Is there any real case scenario for this?Plunk
B
25

It looks like it could be useful for fetching and caching (one-shot) resources, since generally http.get will emit one response then complete.

From rxjs/spec/subjects/AsyncSubject-spec.ts

it('should emit the last value when complete', () => {
it('should emit the last value when subscribing after complete', () => {
it('should keep emitting the last value to subsequent subscriptions', () => {

Components that subscribe after the fetch will then pick up value, which is not the case for Subject

const subject = new Rx.Subject();
const asyncSubject = new Rx.AsyncSubject();

// Subscribe before
subject.subscribe(x => console.log('before complete - subject', x))
asyncSubject.subscribe(x => console.log('before complete - asyncSubject', x))

subject.next('value 1');
subject.complete();
subject.next('value 2');

asyncSubject.next('value 1');
asyncSubject.complete();
asyncSubject.next('value 2');

// Subscribe after
subject.subscribe(x => console.log('after complete - subject', x))
asyncSubject.subscribe(x => console.log('after complete - asyncSubject', x))
.as-console-wrapper { max-height: 100% ! important; top: 0 }
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.js"></script>
Blucher answered 18/1, 2018 at 3:45 Comment(3)
Wow great answer! Really helps explain thingsRodie
So the only difference between this and ReplaySubject(1) would be that ReplaySubject doesn't emit anything until it's complete? So if you only need a single value you'd use AsyncSubject? (don't quote me on this!)Karilynn
ReplaySubject emits whenever it gets a new value. AsyncSubject emits only it's last value, when it sees complete.Blucher
I
2

Cool!

And just for fun, I added handlers to log complete events to show they are raised in either case (Subject or AsyncSubject) when a subscribe happens after a subject is completed.

Also added BehaviorSubject.

const subject = new Rx.Subject();
const asyncSubject = new Rx.AsyncSubject();
const behaviorSubject = new Rx.BehaviorSubject();

console.log('before init - behaviorSubject', behaviorSubject.value)

subject.next('INIT');
asyncSubject.next('INIT');
behaviorSubject.next('INIT');

console.log('before subscribe - behaviorSubject', behaviorSubject.value)

// Subscribe before
subject.subscribe(x => console.log('before complete - subject', x))
asyncSubject.subscribe(x => console.log('before complete - asyncSubject', x))
behaviorSubject.subscribe(x => console.log('before complete - behaviorSubject', x))

subject.next('NEXT');
subject.complete();

asyncSubject.next('NEXT');
asyncSubject.complete();

behaviorSubject.next('NEXT');
behaviorSubject.complete();

// Subscribe after
subject.subscribe({
  next: x => console.log('after complete - subject', x),
  complete: () => console.log('after complete - subject COMPLETE')
})
asyncSubject.subscribe({
  next: x => console.log('after complete - asyncSubject', x),
  complete: () => console.log('after complete - asyncSubject COMPLETE')
})
behaviorSubject.subscribe({
  next: x => console.log('after complete - behaviorSubject', x),
  complete: () => console.log('after complete - behaviorSubject COMPLETE')
})
.as-console-wrapper { max-height: 100% ! important; top: 0 }
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.js"></script>
Incomprehension answered 27/4, 2019 at 9:13 Comment(0)
G
0

Additional Information

AsyncSubject allows you to emit the saved value within the AsyncSubject only when it's been given the complete signal.

Therefore, I could be spamming values into an AsyncSubject, and it won't emit a value until I explicitly send the complete signal to the AsyncSubject. Therefore, it's like a BehaviorSubject (it stores state internally), but it will only emit once you explicitly tell it to.

I don't see where / why I would ever need to use this variant of subject.

The above statement is like asking why would you use a wrench, when you could use a socket wrench. AsyncSubject is just another tool in your toolbelt. You can use it to solve specific problems, and you as the operator get to choose which tool best fits the current problem that you're trying to solve.

AsyncSubject StackBlitz Example

Greave answered 10/4, 2022 at 21:2 Comment(0)
I
0

I find it useful for (e.g.) API calls that your app has to actually fetch once only, but could be accessed again & again over it's lifetime.

Here's some sample code for an angular app:

import { environment } from '../../../environments/environment';
import { Injectable } from '@angular/core';
import { HttpClient, } from '@angular/common/http';
import { AsyncSubject } from 'rxjs';
import { ApiData } from '../models/apidata.model';

@Injectable({ providedIn: 'root' })
export class AppService {

    constructor(
        private http: HttpClient
    ) {
    }

    private _apiData$: AsyncSubject<ApiData>;
    public getApiData(): AsyncSubject<ApiData> {

        if (!this._apiData$) {
            this._apiData$ = new AsyncSubject<ApiData>();
            this.http
                .get<ApiData>(`${environment.baseApiUrl}app/api`)
                .subscribe(
                    o => {
                        this._apiData$.next(o);
                        this._apiData$.complete();
                    },
                    err => {
                        alert("getApiData failed!");
                        console.error(err);
                    });
        }
        return this._apiData$;
    }

}
Inflict answered 6/8, 2022 at 20:6 Comment(1)
Why not use standard Observable then?Belfort

© 2022 - 2024 — McMap. All rights reserved.