Rx distinctUntilChanged allow repetition after configurable time between events - javascript

Let's consider for a moment the following code
Rx.Observable.merge(
Rx.Observable.just(1),
Rx.Observable.just(1).delay(1000)
).distinctUntilChanged()
.subscribe(x => console.log(x))
We expect that 1 is logged just once. However what if we wanted to allow repetition of a value if its last emission was a configurable amount of time ago? I mean to get both events logged.
For example it would be cool to have something like the following
Rx.Observable.merge(
Rx.Observable.just(1),
Rx.Observable.just(1).delay(1000)
).distinctUntilChanged(1000)
.subscribe(x => console.log(x))
In which distinctUntilChanged() accepts some sort of timeout to allow repetition on the next element. However such a thing does not exist and I was wondering if anybody knows an elegant way to achieve this by using high level operators without messing with a filter that would require handling state

Unless I am misunderstanding I am pretty sure this could be accomplished in a relatively straight-forward manner with windowTime:
Observable
.merge(
Observable.of(1),
Observable.of(1).delay(250), // Ignored
Observable.of(1).delay(700), // Ignored
Observable.of(1).delay(2000),
Observable.of(1).delay(2200), //Ignored
Observable.of(2).delay(2300)
)
// Converts the stream into a stream of streams each 1000 milliseconds long
.windowTime(1000)
// Flatten each of the streams and emit only the latest (there should only be one active
// at a time anyway
// We apply the distinctUntilChanged to the windows before flattening
.switchMap(source => source.distinctUntilChanged())
.timeInterval()
.subscribe(
value => console.log(value),
error => console.log('error: ' + error),
() => console.log('complete')
);
See the example here (borrowed #martin's example inputs)

This is an interesting use-case. I wonder whether there's an easier solution than mine (note that I'm using RxJS 5):
let timedDistinctUntil = Observable.defer(() => {
let innerObs = null;
let innerSubject = null;
let delaySub = null;
function tearDown() {
delaySub.unsubscribe();
innerSubject.complete();
}
return Observable
.merge(
Observable.of(1),
Observable.of(1).delay(250), // ignored
Observable.of(1).delay(700), // ignored
Observable.of(1).delay(2000),
Observable.of(1).delay(2200), // ignored
Observable.of(2).delay(2300)
)
.do(undefined, undefined, () => tearDown())
.map(value => {
if (innerObs) {
innerSubject.next(value);
return null;
}
innerSubject = new BehaviorSubject(value);
delaySub = Observable.of(null).delay(1000).subscribe(() => {
innerObs = null;
});
innerObs = innerSubject.distinctUntilChanged();
return innerObs;
})
// filter out all skipped Observable emissions
.filter(observable => observable)
.switch();
});
timedDistinctUntil
.timestamp()
.subscribe(
value => console.log(value),
error => console.log('error: ' + error),
() => console.log('complete')
);
See live demo: https://jsbin.com/sivuxo/5/edit?js,console
The entire logic is wrapped into Observable.defer() static method because it requires some additional variables.
A couple points how this all works:
The merge() is the source of items.
I use do() to properly catch when the source completes so I can shutdown the internal timer and send proper complete notification.
The map() operator is where the most interesting things happen. I reemit the value that it received and then return null if there's already a valid Observable (it was created less then 1000ms ago = innerObs != null). Then I eventually create a new Subject where I'm going to reemit all items and return this BehaviorSubject chained with .distinctUntilChanged(). At the end I schedule 1s delay to set innerObs = null which means then when another value arrives it'll return a new Observable with new .distinctUntilChanged().
Then filter() will let me ignore all null values returned. This means it won't emit a new Observable more than once a second.
Now I need to work with so called Higher-order Observables (Observables emitting Observables. For this reason I use switch() operator that always subscribes only to the newest Observable emitted by the source. In our case we emit Observables only max. once a second (thanks to the filter() used above) and this inner itself Observable can emit as many values it wants and all of them are going to be passed through distinctUntilChanged() so duplicates are ignored.
The output for this demo will look like the following output:
Timestamp { value: 1, timestamp: 1484670434528 }
Timestamp { value: 1, timestamp: 1484670436475 }
Timestamp { value: 2, timestamp: 1484670436577 }
complete
As you can see the value 1 is emitted twice with cca 2s delay. However value 2 passed without any problem after 100ms thanks to distinctUntilChanged().
I know this isn't simple but I hope it makes sense to you :)

Related

async wait for element to load in so i can find it with jquery

i don't really understand async. i have a function like this:
function getTeam() {
let sir = setInterval(() => {
const teamsGrid = $('[class*="teamsgrid"]').find("p");
const firstTeam = $(teamsGrid[0]).text();
if (firstTeam != '') {
clearInterval(sir)
return firstTeam.trim()
}
}, 100)
}
im not js master. i just want to get that element when it loads in, this code is running in a userscript and // #run-at document-idle doesnt help either. i knew i would have to get into async js promises callbacks and whatever someday but i really dont understand how it works after pages of docs and other stackoverflow.
when i console.log this function it will print undefined once then if i have a console.log inside the if it will print the actual team name.
how do i wait for that result
Answer regarding the javascript language part if the question
You could modify your code to the following (but don't - see further below - I'm just providing this as your StackOverflow tags included async/await):
async function getTeam() {
return new Promise(resolve => {
const sir = setInterval(() => {
const teamsGrid = $('[class*="teamsgrid"]').find("p");
const firstTeam = $(teamsGrid[0]).text();
if (firstTeam != '') {
clearInterval(sir);
resolve(firstTeam.trim());
}
}, 100);
});
}
// ... and then anywhere else in your code:
doSomethingSynchronous();
const team = await getTeam();
soSomethingSynchronousWithTeam(team);
Note that this will only work with modern browsers supporting >= ECMAScript 2017:
https://caniuse.com/async-functions (but luckily that's most by now!)
Answer regarding the implicit "howto wait for an element part"
... you really shouldn't actively wait for an element because this is unnecessarily heavy on the CPU. Usually you'll have some kind of event that informs you as soon as the element you're waiting for has been created. Just listen for that and then run your code.
What to do, if there's currently no such event:
If you're in control of the code creating the element, then trigger one yourself (see https://api.jquery.com/trigger/ for example).
If the element is created by a third party lib or by something else you cannot easily modify, you could use a MutationObserver (see this StackBlitz answer to a related question) and run your getTeam code only whenever something has changed instead of every 100ms (smaller impact on performance!)
You can make it async if you want, but the main part us going to be using events instead. There is a special object called mutation observer. It will call a function you give it any time there's a change in the element you're observing.
Check the mutation observer docs to understand the code below: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
Without knowing much about your HTML, I can say as much as this should work:
function getTeam() {
const teamsGrid = $('[class*="teamsgrid"]').find("p");
const firstTeam = $(teamsGrid[0]).text();
if (firstTeam != '') {
return firstTeam.trim()
}
}
function getTeamWhenAvailable() {
// returning a promise allows you to do "await" and get result when it is available
return new Promise((resolve, reject) => {
// furst try if element is available right now
const teamText = getTeam();
if(teamText) {
// resolve() "returns" the value to whoever is doing "await"
resolve(teamText);
// resolve does not terminate this function, we need to do that using return
return;
}
// Mutation observer gives you list of stuff that changed, but we don't care, we just do our own thing
const observer = new MutationObserver(()=>{
const teamText = getTeam();
if(teamText) {
// stop observing
observer.disconnect();
// resolve the value
resolve(teamText);
}
});
observer.observe(document.body, { childList: true, subtree: true };
})
}
// usage, the extra brackets around the lambda cause it to invoke immediatelly
(async () => {
console.log("Waitinf for team...");
const teamName = await getTeamWhenAvailable();
console.log("Result team name: ", teamName)
})();
Now you might wanna narrow the scope of the mutation observer, in the example above it watches the entire document. Try to instead observe the deepest element that you can rely on not being removed.
If you need to receive team name multiple times, I think you should just go with the obsever alone without the async stuff.
function getTeam() {
let sir = new Promise((res, rej) => {
const teamsGrid = $('[class*="teamsgrid"]').find("p");
const firstTeam = $(teamsGrid[0]).text();
if (firstTeam != '') {
clearInterval(sir);
res(firstTeam.trim());
}
});
return sir();
}
From what I understood, you are looking for firstTeam. Also, we assume that there is always firstTeam, so there isnt a case when there would be no team name.
I am not sure where you are making a req that will take time to process honestly from this code. So far it looks that sync function should do just fine. Are you reaching out to any API?

In RxJS, should complete be called in an observable that emits no items?

In my last post, I was trying to buffer pending http requests using RxJS. I thought bufferCount was what I needed, but I found it my items were under the buffer size, it would just wait, which is not what I was after.
I now have a new scheme, using take. It seems to do what I am after, except when my resulting observable has no items (left), the complete is never called.
Eg I have something like the following..
const pendingRequests = this.store$.select(mySelects.getPendingRequests).pipe(
// FlatMap turns the observable of a single Requests[] to observable of Requests
flatMap(x => x),
// Only get requests unprocessed
filter(x => x.processedState === ProcessedState.unprocessed),
// Batches of batchSize in each emit
take(3),
);
let requestsSent = false;
pendingRequests.subscribe(nextRequest => {
requestsSent = true;
this.sendRequest(nextEvent);
},
error => {
this.logger.error(`${this.moduleName}.sendRequest: Error ${error}`);
},
() => {
// **** This is not called if pendingRequests is empty ****
if (requestsSent ) {
this.store$.dispatch(myActions.continuePolling());
} else {
this.store$.dispatch(myActions.stopPolling());
}
}
);
So the take(3) will get the next 3 pending requests and send them ()where I also dispatch an action to set the processed state to not ProcessedState.pending so we don't get them in the next poll)
This all works fine, but when pendingRequests eventually returns nothing (is empty), the completed block, marked with the ****. is not called. I would have thought this would just be called straight away.
I am not sure if this matters, as since I don't then dispatch the action to continue polling, the polling does stop.
But my biggest concern is if pendingRequests is not completed, do I need to unsubscribe from it to prevent any leaks? I assume if the complete is called I do not need to unsubscribe?
Update
To get the pendingRegueststo always complete, I have taken a slightly different approach. Rather than using the rx operators to "filter", I Just get the whole list every time, and just take(1) on it. I will always get the list, even if it is empty, so the pendingReguests will complete every time.
ie
const pendingRequests = this.store$.select(mySelects.getPendingRequests).pipe(take(1))
And then I can just filter and batch inside the observable..
pendingRequests.subscribe(nextRequest => {
let requestToSend = nextRequest.filter(x => x.processedState === ProcessedState.unprocessed);
const totalPendingCount = requestToSend.length;
requestToSend = requestToSend slice(0, this.batchSize);
for (const nextRequest of requestToSend) {
this.sendRequest(nextRequest);
}
if (totalPendingCount > this.batchSize) {
this.store$.dispatch(myActions.continuePolling());
}
In my testing so far, I have now always got the complete to fire.
Also, by having 2 actions (a startPolling, and a continuePolling) I can put the delay just in the continuePolling, so the first time we start the polling (eg the app has just come back online after being out of network range), we submit straight away, and only delay if we have more than the batch size
Maybe this is not 100% the "rxy" way of doing it, but seems to work so far. Is there any problem here?
I would substitute take with toArray and a bit of buffering logic afterwards.
This is how your code could look like. I have added the delay logic, which I think was suggested by your previous post, and provided comments to describe each line added
// implementation of the chunk function used below
// https://www.w3resource.com/javascript-exercises/fundamental/javascript-fundamental-exercise-265.php
const chunk = (arr, size) =>
Array.from({ length: Math.ceil(arr.length / size) }, (v, i) =>
arr.slice(i * size, i * size + size)
);
const pendingRequests = this.store$.select(mySelects.getPendingRequests).pipe(
// FlatMap turns the observable of a single Requests[] to observable of Requests
flatMap(x => x),
// Only get requests unprocessed
filter(x => x.processedState === ProcessedState.unprocessed),
// Read all the requests and store them in an array
toArray(),
// Split the array in chunks of the specified size, in this case 3
map(arr => chunk(arr, 3)), // the implementation of chunk is provided above
// Create a stream of chunks
concatMap((chunks) => from(chunks)),
// make sure each chunk is emitted after a certain delay, e.g. 2 sec
concatMap((chunk) => of(chunk).pipe(delay(2000))),
// mergeMap to turn an array into a stream
mergeMap((val) => val)
);
let requestsSent = false;
pendingRequests.subscribe(nextRequest => {
requestsSent = true;
this.sendRequest(nextEvent);
},
error => {
this.logger.error(`${this.moduleName}.sendRequest: Error ${error}`);
},
() => {
// **** THIS NOW SHOULD BE CALLED ****
if (requestsSent ) {
this.store$.dispatch(myActions.continuePolling());
} else {
this.store$.dispatch(myActions.stopPolling());
}
}
);
I doubt that pendingRequests will ever complete by itself. The Store, at least in ngrx, is a BehaviorSubject. So, whenever you do store.select() or store.pipe(select()), you're just adding another subscriber to the internal list of subscribers maintained by the BehaviorSubject.
The BehaviorSubject extends Subject, and here is what happens when the Subject is being subscribed to:
this.observers.push(subscriber);
In your case, you're using take(3). After 3 values, the take will emit a complete notification, so your complete callback should be called. And because the entire chain is actually a BehaviorSubject's subscriber, it will remove itself from the subscribers list on complete notifications.
I assume if the complete is called I do not need to unsubscribe
Here is what happens when a subscriber(e.g TakeSubscriber) completes:
protected _complete(): void {
this.destination.complete();
this.unsubscribe();
}
So, there is no need to unsubscribe if a complete/error notification already occurred.

RxJS how to ignore an error with catch and keep going

Hi I have the following code and I would like to know how to prevent the main (upstream) Observable from getting deleted when an error is thrown.
How can I change the following code so that all numbers expect '4' get displayed?
I am looking for a general pattern solution that would work in other cases with different operators. This is the simplest case I could come up with.
const Rx = require('rxjs/Rx');
function checkValue(n) {
if(n === 4) {
throw new Error("Bad value");
}
return true;
}
const source = Rx.Observable.interval(100).take(10);
source.filter(x => checkValue(x))
.catch(err => Rx.Observable.empty())
.subscribe(v => console.log(v));
You will want to keep the source observable running, but if you let the error happen on the main event stream it will collapse the entire observable and you will no longer receive items.
The solution involves creating a separated stream where you can filter and catch without letting the upstream pipe collapse.
const Rx = require('rxjs/Rx');
function checkValue(n) {
if(n === 4) {
throw new Error("Bad value");
}
return true;
}
const source = Rx.Observable.interval(100).take(10);
source
// pass the item into the projection function of the switchMap operator
.switchMap(x => {
// we create a new stream of just one item
// this stream is created for every item emitted by the source observable
return Observable.of(x)
// now we run the filter
.filter(checkValue)
// we catch the error here within the projection function
// on error this upstream pipe will collapse, but that is ok because it starts within this function and will not effect the source
// the downstream operators will never see the error so they will also not be effect
.catch(err => Rx.Observable.empty());
})
.subscribe(v => console.log(v));
You could also use the second argument passed into the catch selector to restart the observable source, but this will start it as though it hasn't run before.
const Rx = require('rxjs/Rx');
function checkValue(n) {
if(n === 4) {
throw new Error("Bad value");
}
return true;
}
const source = Rx.Observable.interval(100).take(10);
source.filter(x => checkValue(x))
.catch((err, source) => source)
.subscribe(v => console.log(v));
But this does not achieve the desired effect. You will see a stream that emits 1..3 repeatedly until the end of time... or you shutdown the script. Which ever comes first. (this is essential what .retry() does)
You need to use a flatMap operator where you will do the filtering. In the flatMap in this example I'm using Observable.if() to do the filtering as it guarantees me that I'm returning observables all the time. I'm sure you can do it other ways but this is a clean implementation for me.
const source = Rx.Observable.interval(100).take(10).flatMap((x)=>
Rx.Observable.if(() => x !== 4,
Rx.Observable.of(x),
Rx.Observable.throw("Bad value"))
.catch((err) => {
return Rx.Observable.empty()
})
);
source.subscribe(v => console.log(v));

How to unsubscribe or dispose an interval observable on condition in Angular2 or RxJS?

I'm new to the whole Rx thing and reactive programming, however I have to deal with such a situation. I want a interval observable to check a hardware's status by a POST request to its REST API every 500ms to see if the response changes. So once it changes, I want such interval observable POST request shut down immediately, leaving resource to other future operations. Here is a piece of code.
myLoop(item_array:number[], otheroption : string){
for (let item of item_array){
//set hardware option, still a request
this.configHardware(item,otheroption)
//after config the hardware, command hardware take action
.flatMap(() => {
//this return a session id for check status
this.takeHardwareAction()
.flatMap( (result_id) => {
//I want every 500ms check if hardware action is done or not
let actionCheckSubscription = IntervalObservable.create(500).subscribe(
() => {
//So my question is, can I unsubscribe the actionCheckSubscription in here on condition change? For example,
if (this.intervalCheckingStatus(result_id))
actionCheckSubscription.unsubscribe() ;
}
) ;
})
})
}
}
So you want to make a POST request every 500 ms and then check its response. I assume your method intervalCheckingStatus evaluates the POST response and determines whether it's different?
First, I wouldn't use IntervalObservable.
Have you imported the RxJS module? It's the third party library endorsed by Angular and the one they use in all their developer guide samples. If not, install it and import it.
https://github.com/Reactive-Extensions/RxJS
import * as Rx from 'rxjs/Rx';
I assume you've already imported Http, ResponseOptions etc but here it is in case others are curious:
import { Http, Response, ResponseOptions } from '#angular/http';
EDIT 1: Forgot to include the dependency injection. Inject Http into your constructor. I've called it http, hence how I'm calling this.http.post
constructor(private http: Http) {
Then, I would do the following:
EDIT 2: This would be inside your loop where the post arguments are relevant to the item in your array.
// Every 500 ms, make a POST request
Rx.Observable.interval(500)
// Add your POST arguments here
.map(_ => this.http.post(yourUrl, yourBody))
// This filter here is so that it will only emit when intervalCheckingStatus returns true
// You need to get the property you need from the Response: resp
// Is it the status code you're interested in? That's what I put as an example here but whatever it is, pass it to your method
.filter(resp => this.intervalCheckingStatus(resp.status))
// Take(1) takes only the first emitted value and once it does that, the observable completes. So you do NOT need to unsubscribe explicitly.
.take(1);
If you need to do something once the response has the status (or whatever property) you're looking for, then chain a .subscribe to the end and perform the action you need in there. Again, due to take(1), as soon as the first element is pumped, the observable stream completes and you do not need to unsubscribe.
Also, here is a very helpful website:
http://rxmarbles.com/#take
You can see that in their example, the resulting observable is complete (vertical line) after 2 elements are taken.
You could use Observable.from and concatMap to iterate through all the items and then use a filter in combination with take(1) to stop an interval as soon as the validation passes the filter:
myLoop(item_array:number[], otheroption : string) {
return Observable.from(item_array)
.concatMap(item => this.configHardware(item, otheroption)
.switchMap(resultId => Observable.interval(500)
.switchMapTo(this.intervalCheckingStatus(resultId))
.filter(status => Boolean(status)) // your logic if the status is valid, currently just a boolean-cast
.take(1) // and complete after 1 value was valid
.mapTo(item) // map back to "item" so we can notify the subscriber (this is optional I guess and depends on if you want this feature or not)
)
);
}
// usage:
myLoop([1,2,3,4], "fooBar")
.subscribe(
item => console.log(`Item ${item} is now valid`),
error => console.error("Some error occured", error),
() => console.log("All items are valid now")
);
Here is a live-example with mock-data
const Observable = Rx.Observable;
function myLoop(item_array) {
return Observable.from(item_array)
// if you don't mind the execution-order you can use "mergeMap" instead of "concatMap"
.concatMap(item => configHardwareMock(item)
.switchMap(resultId => Observable.interval(500)
.do(() => console.info("Checking status of: " + resultId))
.switchMap(() => intervalCheckingStatus(resultId))
.filter(status => Boolean(status)) // your logic if the status is valid, currently just a boolean-cast
.take(1) // and complete after 1 value was valid
.mapTo(item) // map back to "item" so we can notify the subscriber (this is optional I guess and depends on if you want this feature or not)
)
);
}
// usage:
myLoop([1,2,3,4])
.subscribe(
item => console.log(`Item ${item} is now valid`),
error => console.error("Some error occured", error),
() => console.log("All items are valid now")
);
// mock helpers
function configHardwareMock(id) {
return Observable.of(id);
}
function intervalCheckingStatus(resultId) {
if (Math.random() < .4) {
return Observable.of(false);
}
return Observable.of(true);
}
<script src="https://unpkg.com/rxjs/bundles/Rx.min.js"></script>

Deferred pattern with RxJS 5 observables

For arbitrary promise implementation, the deferred pattern (not to be confused with antipattern) may may look like:
const deferred = new Deferred;
...
// scopes where `deferred` object reference was passed before promise settlement
deferred.promise.then((result) => { ... }, (error) => { ... });
...
deferred.resolve(...);
// doesn't affect promise state
deferred.reject();
...
// after promise settlement
deferred.promise.then((result) => { ... }, (error) => { ... });
deferred object holds unsettled promise that can be passed to other function scopes by reference. All promise chains will be executed on promise settlement, it doesn't matter if deferred.promise was settled before chaining with then or after. The state of promise cannot be changed after it was settled.
As the answer suggests, the initial choices are ReplaySubject and AsyncSubject.
For the given setup (a demo)
var subject = new Rx.AsyncSubject;
var deferred = subject.first();
deferred.subscribe(
console.log.bind(console, 'Early result'),
console.log.bind(console, 'Early error')
);
setTimeout(() => {
deferred.subscribe(
console.log.bind(console, 'Late result'),
console.log.bind(console, 'Late error')
);
});
This results in desirable behaviour:
subject.error('one');
subject.next('two');
Early error one
Late error one
This results in undesirable behaviour:
subject.error('one');
subject.next('two');
subject.complete();
Early error one
Late result two
This results in undesirable behaviour:
subject.next('two');
subject.complete();
subject.next('three');
Early result two
Late result three
The results from ReplaySubject differ but are still inconsistent with expected results. next values and error errors are treated separately, and complete doesn't prevent the observers from receiving new data. This may work for single next/error, the problem is that next or error may be called multiple times unintentionally.
The reason why first() is used is because subscribes are one-time subscriptions, and I would like to remove them to avoid leaks.
How should it be implemented with RxJS observables?
You are probably looking for a Rx.ReplaySubject(1) (or an Rx.AsyncSubject() depending on your use case).
For a more detailed explanation of subjects, see What are the semantics of different RxJS subjects?.
Basically, a subject can be passed around by reference, like a deferred. You can emit values (resolve would be an 'next' (Rxjs v5) or 'onNext' (Rxjs v4) followed by 'complete' or 'onCompleted()') to it, as long as you hold that reference.
You can have any amount of subscribers to a subject, similar to the then to a deferred. If you use a replaySubject(1), any subscribers will receive the last emitted value, which should answer your it doesn't matter if deferred.promise was settled before chaining with then or after.. In Rxjs v4, a replaySubject will emit its last value to a subscriber subscribing after it has completed. I am not sure about the behaviour in Rxjs v5.
https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/subjects/asyncsubject.md
https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/subjects/replaysubject.md
Update
The following code executed with Rxjs v4 :
var subject = new Rx.AsyncSubject();
var deferred = subject;
deferred.subscribe(
console.log.bind(console, 'First result'),
console.log.bind(console, 'First error')
);
setTimeout(() => {
deferred.subscribe(
console.log.bind(console, 'Second result'),
console.log.bind(console, 'Second error')
);
});
subject.onNext('one');
subject.onCompleted();
subject.onNext('two');
subject.onNext('three');
subject.onNext('four');
produces the following output:
First result one
Second result one
However, the same code executed with Rxjs v5 does not :
First result one
Second result four
So basically that means that subjects' semantics have changed in Rxjs v5!!! That really is a breaking change to be aware of. Anyways, you could consider moving back to Rxjs v4, or use the turnaround suggested by artur grzesiak in his answer. You could also file an issue on the github site. I would believe that the change is intentional, but in the advent it is not, filing the issue might help clarify the situation. In any case, whatever behaviour chosen should definitely be documented properly.
The question about subjects' semantics features a link showing the async subject in relation with multiple and late subscription
As #user3743222 wrote AsyncSubject maybe used in deferred implementation, but the thing is it has to be private and guarded from multiple resolves / rejects.
Below is a possible implementation mirroring resolve-reject-promise structure:
const createDeferred = () => {
const pending = new Rx.AsyncSubject(); // caches last value / error
const end = (result) => {
if (pending.isStopped) {
console.warn('Deferred already resloved/rejected.'); // optionally throw
return;
}
if (result.isValue) {
pending.next(result.value);
pending.complete();
} else {
pending.error(result.error);
}
}
return {
resolve: (value) => end({isValue: true, value: value }),
reject: (error) => end({isValue: false, error: error }),
observable: pending.asObservable() // hide subject
};
}
// sync example
let def = createDeferred();
let obs = def.observable;
obs.subscribe(n => console.log('BEFORE-RESOLVE'));
def.resolve(1);
def.resolve(2); // warn - no action
def.reject('ERROR') // warn - no action
def.observable.subscribe(n => console.log('AFTER-RESOLVE'));
// async example
def = createDeferred();
def.observable.subscribe(() => console.log('ASYNC-BEFORE-RESOLVE'));
setTimeout(() => {
def.resolve(1);
setTimeout(() => {
def.observable.subscribe(() => console.log('ASYNC-AFTER-RESOLVE'));
def.resolve(2); // warn
def.reject('err'); // warn
}, 1000)
}, 1000);
// async error example
const def3 = createDeferred();
def3.observable.subscribe(
(n) => console.log(n, 'ERROR-BEFORE-REJECTED (I will not be called)'),
(err) => console.error('ERROR-BEFORE-REJECTED', err));
setTimeout(() => {
def3.reject('ERR');
setTimeout(() => {
def3.observable.subscribe(
(n) => console.log(n, 'ERROR-AFTER-REJECTED (I will not be called)'),
(err) => console.error('ERROR-AFTER-REJECTED', err));
def3.resolve(2); // warn
def3.reject('err'); // warn
}, 1000)
}, 3000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.0.0-beta.9/Rx.umd.js"></script>

Categories