Firebase onAuthStateChanged unsubscribe recursion - javascript

I have this code that checks to see if a user is already signed in in Firebase, if so, use Redux to dispatch an action and update the state to the current auth user.
/**
* check to see if the user has signed in already or not
*/
function initAuth(dispatch) {
return new Promise((resolve, reject) => {
const unsubscribe = firebase.auth().onAuthStateChanged(
authUser => {
dispatch({ type: "INIT_AUTH", payload: authUser });
unsubscribe();
resolve();
},
error => reject(error)
);
});
}
initAuth(store.dispatch)
.then(() => render())
.catch(error => console.error(error));
What I am confused is, why is the unsubscribe() called within the unsubscribe? I know you can do this as in JavaScript recursion, but what's the use here? Thanks!

onAuthStateChanged takes a function as it's only argument. That function is the one that will be invoked whenever the auth state changes. So the code
function printHelloWorld() {
console.log("Hello World")
}
firebase.auth().onAuthStateChanged(printHelloWorld)
Will print "Hello World" to the console, any time the auth state changes. But, at some later time, we want to stop that function from executing anymore, because we've already done whatever we need to. If you're familiar with event listeners, they use a pattern where to remove one, you would call something like removeEventListener. But firebase does not have a offAuthStateChanged or some such. Instead the onAuthStateChanged function returns a function to you that unsubscribes the function you originally gave it. To be clear, it does not return your original function (the one you gave it, so printHelloWorld in this example), but returns you a new function that can be used to remove the original.
So going back to the example:
function printHelloWorld() {
console.log("Hello World")
}
var unsubscribe = firebase.auth().onAuthStateChanged(printHelloWorld)
// ... Sometime later when we are no longer interested in auth changes
unsubscribe();
// From this point forward, when the auth state changes, printHelloWorld will no longer be triggered.
Finally, suppose that you only want to have a function run on auth changes, but only one time. The simplest way to do that would be to have it run once, then unsubscribe it. So the code:
var unsubscribe = firebase.auth().onAuthStateChanged(() => {
console.log("Hello World")
unsubscribe()
})
means that the first time auth state changes, we will log the string, then immediately unsubscribe from further changes. So by calling the unsubscribe from within the function itself, we are just saying, run one time, then remove yourself.
Also, note that you can call unsubscribe at the beginning or end of the function, it doesn't matter. The entire function body will execute, just like any other. So calling unsubscribe won't halt the execution of the remainder of the function, or anything like that.
This is why things like
var unsubscribe = firebase.auth().onAuthStateChanged(() => {
unsubscribe()
// Lots of other code here...
});
is such a common pattern.

If you want to listen for the changes in the auth status of the user just one time you have to do it this way:
const unsubscribe = firebase.auth().onAuthStateChanged((user) => {
if(unsubscribe) {
unsubscribe();
}
}
It seems the listener runs twice, the first time when is created, and second time when the user actually changes his status. In the first time the unsubscribe is not defined so you to check that is defined before run it.

Related

Why can subscribe be called on subject even after it is completed?

As per most of the blogs we don't need to explicitly unsubscribe the subjects once we call complete. I tried to subscribe the subject after calling complete on it. The complete call back was still executed.
let s = new Subject();
s.complete();
s.subscribe(
() => {
console.log("next");
},
() => {},
() => {
console.log("complete");
}
);
Output: complete
Why subscribing to subject is allowed after it is completed?
What happens in this case is that first, a 'complete' notification is dispatched, and immediately afterwards, the subscription is unsubbed. So it still holds that you don't have to unsub manually.
If you are wondering what's the use of subscribing in the first place, you can think of an example where we apply isEmpty operator to the subject, and do something depending on the value nexted by this operator.

Firebase firestore onSnapshot - Get real-time updates after initial data query using promises

In order to show loading progress, I'm trying to wrap my onSnapshot call in a promise upon initial fetch. Data is loading correctly, but real-time updates are not functioning correctly.
Is there a way implement this type of functionality using the onSnapshot method?
Here's my initial data grab. Real-time updates functioned correctly before implementing the promise wrapper:
const [heroesArr, setHeroesArr] = useState([]);
const db = firebase.firestore();
const dbError = firebase.firestore.FirestoreError;
useEffect(() => {
const promise = new Promise((resolve, reject) => {
db.collection("characterOptions")
.orderBy("votes", "desc")
.onSnapshot(coll => {
const newHeroes = [];
coll.forEach(doc => {
const {
name,
votes
} = doc.data();
newHeroes.push({
key: doc.id,
name,
votes
});
});
if(dbError) {
reject(dbError.message)
} else {
resolve(newHeroes);
}
});
});
promise
.then(result => {
setHeroesArr(result);
})
.catch(err => {
alert(err);
});
}, [db]);
Again, data is being loaded to the DOM, but real-time updates are not functioning correctly.
onSnapshot is not really compatible with promises. onSnapshot listeners listen indefinitely, until you remove the listener. Promises resolve once and only once when the work is done. It doesn't make sense to combine onSnapshot (which doesn't end until you say) with a promise, which resolves when the work is definitely complete.
If you want do get the contents of a query just once, just get() instead of onSnapshot. This returns a promise when all the data is available. See the documentation for more details.
Here's what I think going on with your code: When your component mounts, your promise gets executed once via useEffect and that's where you set state. However, subsequent updates via the onSnapshot listener are not going to change the db reference, and therefore will not trigger useEffect again, and therefore will not execute the promise again, and therefore not set state again.
The only code that will execute when you receive a snapshot update is the callback function within .onSnapshot().
To fix this, I think you could try the following (I'm honestly not sure if it'll work, but worth a try):
Create a variable to track the initial load: let isInitialLoad = true;
Inside your promise.then(), add isInitialLoad = false;
Inside your .onSnapshot(), add if (!isInitialLoad) setHeroesArr(newHeroes); – this way, on initial load setHeroesArr gets executed on the promise but on snapshot updates setHeroesAss gets executed in the .onSnapshot() callback
The downside to this approach is that setHeroesArr will be called immediately upon a snapshot change rather than being wrapped in a promise.
Hope this helps!

What is the difference between subscription.unsubscribe() and subscription.remove()?

I am using Angular 5 and have subscribed an observable using the subscribe() method. I want to know if only calling the unsubscribe() method on the subscription will be sufficient to cleanup everything, or should I also call remove() method?
code snippet:
`
// somewhere in a method
this.s1 = someObservable.subscribe((value) => {
//somecode
});
// in ngOnDestroy
this.s1.unsubscribe(); // should I also call .remove()
`
.remove remove the subscription from an internal list, but it does not unsubscribe.
.unsubscribe clean up everything, do the unsubscribe and remove the observer from the internal list. (There was a bug (fixed) that didn't remove the observer from the list)
.takeWhile keep alive the subscription whilst a certain situation continues to be false
example:
this.service.method()
.subscribe(res => {
//logic
});
this will never unsubscribe.
this.service.method()
takeWhile(() => this.isAlive) // <-- custom variable setted to true
.subscribe(res => {
//logic
});
ngOnDestroy(){
this.isAlive = false;
}
Automatic unsubscribe when the component is going to be destroyed.
this.s1 = someObservable.subscribe((value) => {
//somecode
});
public yourMethod(){
this.s1.unsubscribe();
}
this subscription will exists and be "alive" until yourFunction is not called.
--
I personally like to use the rxjs operator takeWhile to keep the code clean. In a very big project or single component having multiple subscription it's confusing having (IE) 30 variables: Subscription. So If you are asking when to use the takeWhile operator my answer is: (Taking as example one subscription) -> If you are sure that the unsubscribe needs to be done when the component is destroyed, use takeWhile. If you need to unsubscribe in a certain scenario where the component is still "alive", use the second example I wrote.
Hope to have clarified the argument.

Angular 2 Http polling not delivering errors

I am trying to poll a REST API to update a data table which is working fine with the following code:
pollData(url, interval) {
return Rx.Observable.interval(interval)
.mergeMap(() => this.http.get(url));
}
// get data
this.dataService.pollData(this.url, this.updateInterval)
.subscribe(
data => console.log(data),
err => console.log(err),
() => console.log('done'));
The problem is that error and complete never get called. Any suggestions to get this working with onError and onCompete would be greatly appreciated. Thanks!
About the onComplete call on the observer, it will be effected only when the source observable finishes. This means when the observable returned by pollData completes. As you are currently polling with no exit condition, then naturally your observable never completes.
To have this observable complete, you need to come up with an exit condition :
timeout (for instance, poll for X seconds, then stop polling)
number of polls
pollData-based condition (for instance, if no changes detected after X consecutive polling)
external completion signal
any other condition which makes sense to your use case
All these conditions are easy to implement with RxJS through they will require you to update the code of the pollData function.
For instance for the external completion signal, you could write :
// defining somewhere the subject for signalling end of polling
stopPollingS = new Rx.Subject();
// somehow pass this subject as a parameter of the polling function
pollData(url, interval, stopPollingS) {
return Rx.Observable
.interval(interval)
.mergeMap(() => this.http.get(url))
.takeUntil(stopPollingS);
}
// somewhere in your code when you want to stop polling
stopPollingS.onNext(true);
About the onError call on the observer, , I am not sure I get what is happening. Have you tried provoking an error and check the onError handler of your observer is indeed called? If there is no error, it is quite obvious that the onError will not be called.
Just in case anyone was wanting to know how I went about solving this problem and implemented the functionality that was required. Basically I just needed to wrap the observable in another and return the error as a data.
initiatePolling(url, interval) {
var http = this.http;
return Rx.Observable.create(function (observer) {
// initial request (no delay)
requestData();
var timerId = setInterval(requestData, interval);
function requestData() {
var subscription = http.get(url).timeout(20000)
.subscribe(
result => {
observer.next(result);
subscription.unsubscribe();
},
err => {
observer.next(err);
subscription.unsubscribe();
},
() => {
subscription.unsubscribe();
});
}
return function () {
observer.complete();
window.clearInterval(timerId);
}
});
}

Ensure order that subscribers get updated

Is there a way to make sure the order on how subscribers get updated is ensured?
I've got a hot observable and my first subscriber does some sync work to update a variable and my next subscriber then has to initialise a service (only once!), and only after that variable is ensured to be set!
it looks like this:
import App from './App'
var appSource = App.init() // gets the hot observable
// our second subscriber
appSource.take(1).subscribe(() => {
// take 1 to only run this once
nextService.init()
})
where App.init looks like this:
...
init() {
var source = this.createObservable() // returns a hot interval observable that fetches a resource every few minutes
// first subscriber, updates the `myVar` every few minutes
source.subscribe((data) => this.myVar = data)
return source
}
...
this currently works, but I am unsure if it will always follow the order 100%.
EDIT:
As I've heard, subscribers will be invoked FIFO. So the order is somewhat assured.
I don't know if RxJS ever explicitly guarantees that observers are called in order of subscription. But, as you say, it usually works.
However, you might consider modelling your actual workflow instead of relying on implicit observer order.
It sounds like you need to know when your app is initialized so you can take further action. Instead of relying on knowledge of the internal workings of App.init, App could expose an API for this:
One (non-Rx way) is to let the caller supply a callback to init:
//...
init(callback) {
var source = this.createObservable() // returns a hot interval observable that fetches a resource every few minutes
// first subscriber, updates the `myVar` every few minutes
source.subscribe((data) => {
this.myVar = data;
if (callback) {
callback();
callback = undefined;
}
})
return source
}
// elsewhere
App.init(() => nextService.init());
Another option instead of a callback is to just have init return a Promise that your resolve (or an Rx.AsyncSubject that you signal) once initialization is complete.
And yet another option, but requires a bit of a refactor, is to model this.myVar as the observable data that it is. i.e.:
init() {
this.myVar = this.createObservable().replay(1);
this.myVar.connect();
// returns an observable that signals when we are initialized
return this.myVar.first();
}
// elsewhere, you end up with this pattern...
const servicesToInit = [ App, service1, service2, service3 ];
Observable
.of(servicesToInit)
.concatMap(s => Rx.Observable.defer(() => s.init()))
.toArray()
.subscribe(results => {
// all initializations complete
// results is an array containing the value returned by each service's init observable
});
Now, anything that wants to make use of myVar would always need to subscribe to it in someway to get the current and/or future values. They could never just synchronously ask for the current value.

Categories