In the following code, I create a simple observable that produces one value and then complete. Then I share that observable replaying the last item and suscribe 3 times. The first right after, the second one before the value is produced and the third time after value is produced and the observable has completed.
let i = 0;
let obs$ = Rx.Observable.create(obs => {
console.log('Creating observable');
i++;
setTimeout(() => {
obs.onNext(i);
obs.onCompleted();
}, 2000);
}).shareReplay(1);
obs$.subscribe(
data => console.log(`s1: data = ${data}`),
() => {},
() => console.log('finish s1')
);
setTimeout( () => {
obs$.subscribe(
data => console.log(`s2: data = ${data}`),
() => {},
() => console.log('finish s2')
);
}, 1000);
setTimeout( () => {
obs$.subscribe(
data => console.log(`s3: data = ${data}`),
() => {},
() => console.log('finish s3')
);
}, 6000);
You can execute this on jsbin
This results in the following marble diagram
Actual
s1: -----1$
s2: \--1$
s3: \1$
But I would expect
Expected
s1: -----1$
s2: \--1$
s3: \----2$
I can understand why someone would like to have the first behaviour, but my reasoning is that, unlike this example, where I'm returning a number, I could be returning an object susceptible to unsubscribe behaviour, for example a database connection. If the above marble diagram represents a database connection, where in the dispose method I call a db.close(), on the third subscription I would have an exception, because I'm receiving as value a database handler that was released. (because when the second subscription finished refCount = 0 and the source is disposed).
Also another weird thing this example has, is that even it's resolving with
the first value and completing just after, its subscribing to the source twice (as you can see by the duplicated "Creating observable")
I know this github issue talks about this but what I'm missing is:
How can achieve (both in RxJs4 and 5) a shared observable that can replay the last item if the source observable hasn't completed, and if its done (refCount = 0), recreate the observable.
In RxJs5 I think the share method solves the reconnecting part of my problem, but not the sharing part.
In RxJs4 I'm clueless
If possible I would like to solve this using existing operators or subjects. My intuition tells me I would have to create a different Subject with such logic, but I'm not quite there yet.
A bit on shareReplay:
shareReplay keeps the same underlying ReplaySubject instance for the rest of the lifetime of the returned observable.
Once ReplaySubject completes, you can't put any more values into it, but it will still replay. So...
You subscribe to the observable the first time and the timeout starts. This increments i from 0 to 1.
You subscribe to the observable the second time and the timeout is already going.
The timeout callback fires and sends out onNext(i), then onCompleted().
onCompleted() signal completes the ReplaySubject inside the shareReplay, meaning that from now on, that shared observable will simply replay the value it has (which is 1) and complete.
A bit on shared observables in general:
Another, separate issue is that since you shared the observable, it's only ever going to call the subscriber function one time. That means that i will only ever be incremented one time. So even if you didn't onCompleted and kill your underlying ReplaySubject, you're going to end up not incrementing it to 2.
This isn't RxJS 5
A quick way to tell is onNext vs next. You're currently using RxJS 4 in your example, but you've tagged this with RxJS 5, and you've sighted an issue in RxJS 5. RxJS 5 is beta and a new version that is a complete rewrite of RxJS 4. The API changes were done mostly to match the es-observable proposal which is currently at stage 1
Updated example
I've updated your example to give you your expected results
Basically, you want to use a shared version of the observable for the first two calls, and the original observable for the third one.
let i = 0;
let obs$ = Rx.Observable.create(obs => {
console.log('Creating observable');
i++;
setTimeout(() => {
obs.onNext(i);
obs.onCompleted();
}, 2000);
})
let shared$ = obs$.shareReplay(1);
shared$.subscribe(
data => console.log(`s1: data = ${data}`),
() => {},
() => console.log('finish s1')
);
setTimeout( () => {
shared$.subscribe(
data => console.log(`s2: data = ${data}`),
() => {},
() => console.log('finish s2')
);
}, 1000);
setTimeout( () => {
obs$.subscribe(
data => console.log(`s3: data = ${data}`),
() => {},
() => console.log('finish s3')
);
}, 6000);
Unrelated
Also, protip: be sure to return a cancellation semantic for your custom observable that calls clearTimeout.
Related
I'm expecting the following RxJS behavior: for every element in the source array, execute an action (the part commented out) which needs to be awaited to complete, and only then fetch next element in source array, wait again and so on.
But the behavior I get instead, is all elements in the source array are fetched at the same time, then after the delay they are retried all again etc.
import { from, defer, delay, repeat, tap } from 'rxjs';
const source$ = from([1, 2, 3])
const actions$ = source$.pipe(
tap((t) => console.log(t))
// ... action that takes long and needs to be waited for, before going to the next element in source$
)
const timedExecution$ = defer(() => actions$).pipe(
delay(3000),
repeat(3)
)
timedExecution$.subscribe();
I also tried another way, with timer:
import { from, tap, timer } from 'rxjs';
const source$ = from([1, 2, 3])
const actions$ = source$.pipe(
() => timer(0, 3000),
tap((t) => console.log(t))
// actionThatTakesLong() action that takes long and needs to be waited for, before going to the next element in source$
)
actions$.subscribe();
Here, it emits one at a time, but sometimes the actionThatTakesLong() takes longer than the arbitrary 3000 MS value of the timer, and i need it to wait until its done, instead of a hardcoded value of waiting.
Thanks for any hints in advance
Your source Observable is from() which is a synchronous Observable that emits array items one after another immediately on subscription. It doesn't (and can't) care what happens with the values in the chain.
delay() will take each value and delay it by a certain time but it doesn't (and can't) care whether the previous values have reached your observer. It just takes each value and delays it by 3s without waiting for the previous delay to complete so in your case it appears like all values were emitted at the same time.
What you want to do instead is adding concatMap() operator that will wait until the nested delayed Observable completes:
from([1, 2, 3])
.pipe(
concatMap(value => of(value).pipe(delay(3000))),
)
.subscribe(...);
FYI, the second option you are mentioning does something very different than you think:
const actions$ = source$.pipe(
() => timer(0, 3000),
tap(() => ...),
);
This is actually replacing the source Observable from() with a different Observable timer(0, 3000). You're basically using approach used for creating custom operators https://rxjs.dev/guide/operators#creating-new-operators-from-scratch.
My concern is if using the same isLoading boolean state can cause concurrency issues. Here's an example of what I'm talking about:
const [isLoading, setIsLoading] = useState<boolean>(false);
useEffect(() => {
async function getWaitlistDetailsOnLoad() {
setIsLoading(true)
try {
const details = await getWaitlistDetailsForUser(
apiInstance,
product.id
);
if (details.status === 200) {
setWaitlistDetails(details.data);
}
} catch (e) {
console.log("Error getting waitlist details: ", e);
}
setIsLoading(false)
}
getWaitlistDetailsOnLoad();
}, [apiInstance, product.id]);
useEffect(() => {
async function getNextAvailableTimeOnLoad() {
setIsLoading(true)
try {
const time = await getNextAvailableTime(apiInstance, product.id);
if (time.status === 200) {
setNextAvailableTime(time.data);
}
setIsLoading(false);
} catch (e) {
console.log("Error getting next available time: ", e);
}
setIsLoading(false)
}
getNextAvailableTimeOnLoad();
}, [apiInstance, product.id]);
Of course I can just track two independent loading states like this:
const [firstLoader, setFirstLoader] = useState<boolean>(false);
const [secondLoader, setSecondLoader] = useState<boolean>(false);
...but if I don't have to, I'd rather not. It would make the code simpler and conditional rendering simpler as well for my use-case.
If you are guaranteed that the two asynchronous effects will not run at the same time (for instance, if OperationTwo, if one is called at the completion of OperationOne) then you could get away with using a single isLoading boolean, set as true at the start of OperationOne, and set as false at the completion of OperationTwo.
However, if you have two operations that might at any point run at the same time, then you should split them into two separate loaders, and use a single value that ORs them to determine the ultimate loading state of the view.
Let's consider a component that makes two asynchronous fetches on load:
const MyPretendComponent = () => {
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setIsLoading(true);
MyService.fetchThatCompletesInOneSecond()
.then(() => setIsLoading(false));
MyService.fetchThatCompletesInTwentySeconds()
.then(() => setIsLoading(false));
}, [])
return (<h1>{`isLoading ? "Loading" : "Finished!"`}</h1>);
}
This should illustrate the problem nicely-- we have two asynchronous operations on component load-- one that completes in one second, and one that completes in twenty seconds. Both set isLoading as false when they complete. In this case, the first operation completes in one second and sets isLoading to false, and the UI erroneously reports that it is not in a loading state even though the second operation still has nineteen seconds left until it completes.
The cleaner version, using two booleans, is this:
const MyPretendComponent = () => {
const [isFirstOperationLoading, setIsFirstOperationLoading] = useState(false);
const [isSecondOperationLoading, setIsSecondOperationLoading] = useState(false);
useEffect(() => {
setIsFirstOperationLoading(true);
setIsSecondOperationLoading(true)
MyService.fetchThatCompletesInOneSecond()
.then(() => setIsFirstOperationLoading(false));
MyService.fetchThatCompletesInTwentySeconds()
.then(() => setIsSecondOperationLoading(false));
}, [])
const isLoading = isFirstOperationLoading || isSecondOperationLoading;
return (<h1>{`isLoading ? "Loading" : "Finished!"`}</h1>);
}
Here we've split the two loading states into their own discrete booleans. We still maintain a single isLoading boolean that simply ORs both the discrete booleans to determine if the overall loading state of the component is loading or not loading. Note that, in a real-life example, we would want to use much more semantically descriptive variable names than isFirstOperationLoading and isSecondOperationLoading.
In regards to splitting the two calls into separate useEffects: at a glance one might think that in splitting the two calls across two different useEffects you could mitigate or sidestep the issue of asynchronicity. However, if we walk through the component lifecycle, and think we will learn that this is not actually effective. Let's update our first example:
const MyPretendComponent = () => {
const [isFirstOperationLoading, setIsFirstOperationLoading] = useState(false);
const [isSecondOperationLoading, setIsSecondOperationLoading] = useState(false);
useEffect(() => {
setIsFirstOperationLoading(true);
setIsSecondOperationLoading(true)
MyService.fetchThatCompletesInOneSecond()
.then(() => setIsFirstOperationLoading(false));
MyService.fetchThatCompletesInTwentySeconds()
.then(() => setIsSecondOperationLoading(false));
}, [])
const isLoading = isFirstOperationLoading || isSecondOperationLoading;
return (<h1>{`isLoading ? "Loading" : "Finished!"`}</h1>);
}
The problem here is that the component does not run in this manner:
Run the first useEffect
When the first useEffect completes, run the second useEffect
When the second useEffect completes, render the UI
If it ran in that manner, we would be sitting and waiting for it to show the UI while it loaded.
Instead, the way it runs is like this:
Run the component render-- call any useEffects if applicable, then render the UI
Should any state updates occur (for instance, a state hook being called from the .then of a previous useEffect asynchronous call) then render again
Repeat step two every time a state or prop update occurs
Keeping this in mind, at every component render React will evaluate and run any and all applicable useEffect callbacks. It is not running them serially and tracking if one has completed-- such a thing would be difficult if not impossible. Instead, it is the developer's responsibility to track loading states and organize useEffect dependency arrays in such a way that your component state is logical and consistent.
So you see, separating the async calls by useEffect does not save us from dealing with potential concurrent async calls. In fact, if it did, it would mean that the UI load would be slower overall, because you would be serially calling two calls that could be called simultaneously. For instance, if both async calls took ten seconds each, calling them one after the other would mean it would take 20 seconds before loading would be completes, as opposed to running them at the same time and finishing loading after only ten seconds. It makes sense from a lifecycle perspective for the component to behave this way; we simply must code accordingly.
As #alexander-nied already pointed out there might by a bug in your code. useEffect callbacks can't be async. To use async/await nontheless you can wrap the three lines as follows and add an await statement.
// Version 1, closer to the original example
useEffect(() => {
async doSomething() {
await someAsyncMethod();
}
(async () => {
setIsLoading(true);
await doSomething();
setIsLoading(false);
})();
})
// Version 2, closer to the current example
useEffect(() => {
async doSomething() {
setIsLoading(true);
await someAsyncMethod();
setIsLoading(false);
}
doSomething();
})
To answer your question:
It certainly depends on you usecase, but I would recommend against just using one single boolean, since it allows for weired scenarios to occur.
What if one request is way faster than the other? Your loading indicator would be set to false, instead of informing the user that there is still work being done in the background.
I am getting the error below. I assume this is because I'm still subscribed to the Firebase database even when my component unmounts. I am trying to take advantage of the real-time features so that whenever an item is deleted from the list it will reflect on the UI.
I have created multiple functions with a single purpose to fetch different documents. Below is one example.
export const getAllTask = (userId) => {
return new Promise((resolve, reject) => {
const db = database.ref('tasks');
db.child(`${userId}/taskItems`).on('value', (snapshot) => {
const user = snapshot.val();
if (user) {
resolve(user);
} else {
reject(user);
}
});
});
};
Then whenever any of my components, it runs my useEffect to fetch data however when it unmounts how can I correctly use off() or clean up correctly? Is there a better approach to do this?
useEffect(() => {
const test = async (userId) => {
await getAllTask(userId).then((result) => {
setItems(result);
});
};
test(userId);
}, [userId]);
try this ,useEffect returns a function that is called when component unmounts
useEffect(() => {
let mounted=true;
const test = async (userId) => {
await getAllTask(userId).then((result) => {
if(mounted) setItems(result)
});
};
test(userId);
return ()=>{mounted=false}
}, [userId]);
In this case, you shouldn't use on() at all. Use once() for reading data a single time. It returns a promise that's easy to work with. on() is for persistent listeners that deliver updates over time every time something changes with the results of the query.
Since promises can only resolve a single time, it doesn't make sense to wrap a persistent listener in a promise.
If you do actually want listener updates over time, for as long as the component is mounted, don't wrap it in a promise. instead, you should instruct your useEffect to unsubscribe the listener by returning a function that it can invoke to shut things down.
See also: How to properly update state with Firebase and useEffect()? In the answer there, see how the useEffect hook returns a function that calls off().
I have few Observables like this one in my code.
this.server.doRequest().subscribe(response => console.log(response)
error => console.log(error),
() => {
console.log('completed');
});
There could be any number of these Observables,
so I need to write a function that checks if each Observable is done otherwise waits till each is finished.
I'm assuming I can create an array push every new Observable there and when it's completed remove it by index. But is it good solution?
Where I want to use it. For example I have a page where user upload photos any amount asynchronously and then he press Finish button. Once he pressed Finish button I need to wait till ALL dynamically created Observables are completed.
you should use higher order observables for this, your exact use case will dictate the exact operator, but forkJoin seems a good candidate:
forkJoin(
this.server.doRequest1(),
this.server.doRequest2(),
this.server.doRequest3(),
this.server.doRequest4()
).subscribe(vals => console.log('all values', vals));
forkJoin won't emit till all innter observables have completed. making it the operator of choice for waiting for multiple observables to complete. You can also feed it an array of observables. There are multiple other operators that may fulfill your case too, such as concat, merge, combineLatest or a few others.
edit based on more details:
in the use case described in your update, you'll still want to use a higher order observable, but forkjoin is not what you want. you'll want to use a local subject to accomplish the goal as wanting to kick off each observable as it is selected and waiting for them all to be done complicates things a little (but not too much):
suppose you had a template like:
<button (click)="addPhoto()">Add Photo</button>
<button (click)="finish()">Finish</button>
where the add photo button gets the users photo and all that, and finish is your completion, you could have a component like this:
private addPhoto$ = new Subject();
constructor() {
this.addPhoto$.pipe(
mergeMap(() => this.uploadPhoto()),
).subscribe(
(resp) => console.log('resp', resp),
(err) => console.log('err', err),
() => console.log('complete')
);
}
private uploadPhoto() {
// stub to simulate upload
return timer(3000);
}
addPhoto() {
this.addPhoto$.next();
}
finish() {
this.addPhoto$.complete();
}
if you run this code, you'll see that the photo adds will emit in the subscribe handler as they complete, but complete will only fire once all the photo uploads have completed and the user has clicked finish.
here is a stackblitz demonstrating the functionality:
https://stackblitz.com/edit/angular-bsn6pz
I'd create a dictionary (in javascript that would be a JSON with observable names as boolean properties) where you push each observable on "create" and a method which should execute on completion of each observable, which will iterate through that dictionary and if all completed do something.
That will ensure parallelism and final execution after all completed.
var requests = {
doRequest1: false,
doRequest2: false,
doRequest3: false
};
var checkIfCAllCompleted = name => {
requests[name] = true;
for (var property in requests) {
if (object.hasOwnProperty(property)) {
if (!property) {
return;
}
}
}
// all properties are true - do something here
console.log("here");
}
this.server.doRequest1().then(() => checkIfCAllCompleted("doRequest1"));
this.server.doRequest2().then(() => checkIfCAllCompleted("doRequest2"));
this.server.doRequest3().then(() => checkIfCAllCompleted("doRequest3"));
I want to make HTTP request to an API endpoint again and again (forever), use previous value on every step (providing live "delta"). That API endpoint is throttled so I cannot make more than 5 requests per minute.
So if previous request took > 1/5 minute then we fire next request immediately. Otherwise we wait for 1/5 minute.
As a mean of using previous value on each step I thought about expand but how to combine this with interval/timer not to make requests too often?
You can consider to create an Observable invoking a function such as the following
const callStream = (apiThrottleTime: number) => {
let inputFromPreviousCall;
return interval(apiThrottleTime).pipe(
mergeMap(i => {
console.log('Call counter: ' + i, 'Input from previous call: ' + inputFromPreviousCall);
return callSimulation(inputFromPreviousCall)
}, 1),
tap(result => inputFromPreviousCall = result),
);
}
and then subscribe to it, passing the throttle time of your API as input variable, e.g. like
const apiThrottleTime = 1000;
callStream(apiThrottleTime).subscribe();
the API call is simulated with this code
let callCounter = 0;
const callSimulation = (inputFromPreviousCall: number) => {
const call = new Subject<any>();
setTimeout(() => {
call.next(callCounter);
callCounter++;
call.complete();
}, Math.random()*4000);
return call.asObservable();
};
The idea is the following
With interval you create the rhythm of notification you want to
have at the base, which is the throttle time of your API
Each time this rhythm clock ticks, then you call the API, which
returns an Observable
You create a new stream, merging such new Observables with
mergeMap, keeping the concurrency level at 1 - the concurrency
level is set via the second optional parameter of mergeMap
The fact that you have the concurrency level set to 1 ensures that the
sequence of notifications of the resulting Observable is maintained
the same as the sequence of notifications of the rhythm clock,
i.e. the sequence of the calls to the API is maintained, which is
crucial if you want to use the result of the previous call as input
to the next - note the mergeMap with concurrency level set to 1 is the same as concatMap
The result of a call is stored in the variable inputFromPreviousCall, local to the function callStream, and is used in the subsequent call to the API
How about this:
export const onePerInterval = <T>(intervalMs: number) =>
(observable: Observable<T>) => observable.pipe(
concatMap(val => interval(intervalMs).pipe(take(1), map(() => val)))
)