I have the following code in my service:
public loginWithFacebook(): Observable<any> {
console.log('Login');
return Observable.fromPromise(this.fb.login()).flatMap((userData) => {
return this.http.post(authFacebook, {access_token: userData.authResponse.accessToken}, { observe: 'response' });
}).do( (response: HttpResponse<any>) => {
const token = response.headers.get('x-auth-token');
if (token) {
localStorage.setItem('id_token', token);
}
});
}
getFacebookProfile():Observable<any> {
console.log("Get Profile");
return Observable.fromPromise(this.fb.getLoginStatus())
.filter((state) => state.status === 'connected')
.switchMapTo(Observable.fromPromise(this.fb.api('/me')));
}
And later I use it in my component to get the profile info once login is successful.
this.profileData = this.usersService.loginWithFacebook()
.flatMapTo(this.usersService.getFacebookProfile());
However, for some reason getFacebookProfile() fires instantly even before the login procedure is complete. And I get an authentication error. Also, I have to login twice to get profile info displayed.
I've been always thinking that switchMap and flatMap only switch to the next observable once the previous one emits a value.
What am I doing wrong here?
--EDIT--
If I subscribe to the first Observable and call getFacebookProfile() in the subscription, everything works normally. But it's not very elegant solution I feel.
The problem is that promises are eager. You are calling this.fb.login() when you compose your observable and you are passing the returned promise into fromPromise.
That means that the login is initiated when loginWithFacebook is called and not when subscribe is called on the observable it returns.
If you want the login to be deferred until subscribe is called, you can use defer:
public loginWithFacebook(): Observable<any> {
console.log('Login');
return Observable.defer(() => Observable.fromPromise(this.fb.login()))
.flatMap((userData) => {
return this.http.post(authFacebook, {
access_token: userData.authResponse.accessToken
}, { observe: 'response' });
})
.do( (response: HttpResponse<any>) => {
const token = response.headers.get('x-auth-token');
if (token) {
localStorage.setItem('id_token', token);
}
});
}
For more information on using observables and promises, see Ben Lesh's article: RxJS Observable interop with Promises and Async-Await
It worked at last thanks to #cartant's answer. However, for some reason, I had to wrap it with defer operator twice. I would be thankful if someone could explain why it was necessary to do it. It's a bit weird.
public loginWithFacebook(): Observable<any> {
return Observable.defer(() =>
Observable.defer(() => this.fb.login()).flatMap((userData) =>
{
return this.http.post(authFacebook, {access_token: userData.authResponse.accessToken}, { observe: 'response' });
}).do( (response: HttpResponse<any>) => {
const token = response.headers.get('x-auth-token');
if (token) {
localStorage.setItem('id_token', token);
}
})
);
}
getFacebookProfile():Observable<any> {
return Observable.defer(() =>
Observable.defer(() => this.fb.getLoginStatus())
.filter((state) => state.status === 'connected')
.switchMapTo(Observable.fromPromise(this.fb.api('/me')))
);
}
Related
I start to use RTK query and try to update headers with new access tokens but found it's hard to update the access in state. More specifically, there is no way to access dispatch inside prepareHeaders. Only getState is exposed.
createApi({
reducerPath: 'baseApi',
baseQuery: fetchBaseQuery({
baseUrl: "/",
prepareHeaders: (headers, { getState } ) => {
const { token, expiration } = getState().auth
if (expiration > new Date()) {
if (token) {
headers.set('authorization', `Bearer ${token}`)
}
} else {
// if token expired, get a new token
try {
const result = await fetch("/token_store")
//dispatch(setNewToken(result)) ---> how to use dispatch here?
} catch (err) {
console.log("something goes wrong.")
}
}
return headers
}),
endpoints: () => ({}),
});
You should definitely not do that in prepareHeaders - there is a reason why there is no dispatch available there.
For something like that, you would wrap your baseQuery.
There are some examples of wrapping the basequery here in the documentation
I try to use the Ionic Storage module to store some values, for example my authentication token :
/**
* Get Token
*/
public get token(): string {
this.storage.get(this.LS_TOKEN).then((val) => {
console.log(val);
this._token.next(val);
console.log( this._token.getValue());
});
return this._token.getValue();
// return 'testtttt';
}
I try multiple things, return directly the value, set the value and return the variable...
But I always got a null, and the thing that is strange is that if I return a string directly it works, when I console.log the val it show the string that I want, but the return is always null..
What am I doing wrong ?
Edit :
In response of the first answer I have tried this :
/**
* Get Token
*/
public get token() {
this.tokenPromise().then(yourToken => {
console.log(yourToken);
return yourToken;
});
}
public tokenPromise() {
return new Promise((resolve, reject) => {
this.storage.get(this.LS_TOKEN).then((val) => {
resolve(val);
}).catch(ex => {
reject(ex);
});
});
}
My problem is the same, in my components when I try to use : console.log(this.sharedService.token);
It's still null
It is not working with your new token() method.
It is still asnychron. Im gonna show you:
public get token() {
return new Promise((resolve, reject)=>{
this.storage.get(this.LS_TOKEN).then((val) => {
resolve(val);
}).catch(ex=>{
reject(ex);
});
});
}
Now you can use your token from the sharedservice like this:
this.sharedService.token.then(token=>{
//use token here;
});
or you can use await, but the function who is calling it, must be async:
async useTokenFromService(){
let token = await this.sharedService.token;
console.log(token);
}
You are getting a Promise from the storage.get() method.
This means it is running asynchron.
You can return Promise.
public get token() {
return new Promise((resolve, reject)=>{
this.storage.get(this.LS_TOKEN).then((val) => {
resolve(val);
}).catch(ex=>{
reject(ex);
});
});
}
And you can receive this with an async function and await the result:
async loadToken(){
let loadedToken = await this.token();
// use your loadedToken here...
}
Or you can use the .then method from the promise like this:
loadToken(){
this.token().then(yourToken=>{
// use the token yourToken here...
});
}
With axios the code is:
export const createBlaBla = (payload) => {
return axios.post('/some-url', payload)
.then(response => response)
.catch(err => err);
}
And then I'm using this with redux-saga like this:
function* createBlaBlaFlow(action) {
try {
const response = yield call(createBlaBla, action.payload);
if (response) {
yield put({
type: CREATE_BLA_BLA_SUCCESS
});
}
} catch (err) {
// I need the error data here ..
yield put({
type: CREATE_BLA_BLA_FAILURE,
payload: 'failed to create bla-bla'
});
}
}
In case of some error on the backend - like invalid data send to the backend - it returns a 400 response with some data:
{
"code":"ERR-1000",
"message":"Validation failed because ..."
"method":"POST",
"errorDetails":"..."
}
But I don't receive this useful data in the catch statement inside the saga. I can console.log() the data in the axios catch statement, also I can get it inside the try statement in the saga, but it never arrives in the catch.
Probably I need to do something else? ... Or the server shouldn't return 400 response in this case?
So, I came up with two solutions of this problem.
===
First one - very dump workaround, but actually it can be handy in some specific cases.
In the saga, right before we call the function with the axios call inside, we have a variable for the errors and a callback that sets that variable:
let errorResponseData = {};
const errorCallback = (usefulErrorData) => {
errorResponseData = usefulErrorData;
};
Then - in the axios method we have this:
export const createBlaBla = (payload, errCallback) => {
return axios.post('/some-url', payload)
.then(response => response)
.catch(err => {
if (err && err.response.data && typeof errCallback === 'function') {
errCallback(err.response.data);
}
return err;
});
}
This way, when we make request and the backend returns errors - we'll call the callback and will provide the errors from the backend there. This way - in the saga - we have the errors in a variable and can use it as we want.
===
However, another solution came to me from another forum.
The problem I have is because in the method with the axios call I have catch, which means that the errors won't bubble in the generator. So - if we modify the method with the axios call like this:
export const createBlaBla = (payload) => {
return axios.post('/some-url', payload)
}
Then in the catch statement in the saga we'll have the actual backend error.
Hope this helps someone else :)
In your API call you can do the following:
const someAPICall = (action) => {
return axios.put(`some/path/to/api`, data, {
withCredentials: true,
validateStatus: (status) => {
return (status == 200 || status === 403);
}
});
};
Please note the validateStatus() part - this way when axios will encounter 200 or 403 response, it will not throw Error and you will be able to process the response after
const response = yield call(someAPICall, action);
if (response.status === 200) {
// Proceed further
} else if (response.status === 403) {
// Inform user about error
} else {
...
}
What I want is to make GET http request in my provider, wrap http call in Observable.create and return Observable object, then in my component, resolve that Observable. I tried something, can someone tell me if it is ok (I can't test at the moment so don't know if it works)? If it's not ok, can someone guide me what is the best practice in Ionic 3, to do http call and handle response (success, failure)?
In provider:
public getUsers(credentials):Observable<any> {
if("some custom condition not satisfied")
return Observable.throw("ERROR);
return Observable.create(observer => {
this.http.get(API_URL + '/users').subscribe(
(response) => {
observer.next(response);
observer.complete();
},
(error)=> {
return Observable.throw(error);
});
});
}
then in component:
this.myProvider.login().subscribe(response => {
if (response.status == 403) {
this.loading.dismiss();
console.log("Access denied");
}
this.loading.dismiss();
this.nav.setRoot('MainPage');
},
error => {
this.loading.dismiss();
console.log(error);
});
I am new to Ionic 2 and Promises and having some issues.
My Ionic 2 app saves an auth_token to the local storage:
this.storage.set('auth_token', auth_token);
Then later in my secured component I want to check if a token is set, but I don't know how to do this.
I tried this:
authenticate() {
var auth_token = this.storage.get('auth_token').then((val) => {
return val;
});
}
Then from somewhere else I called:
console.log(this.auth.authenticate);
But it won't work, it just returns the function itself.
How do I return the token from my authenticate method?
Check here for chaining of promises.
In your authenticate() function return the original promise call and use then in the function in the other location
authenticate() {
return this.storage.get('auth_token').then((val) => {
return val;
});
}
When caling authenticate...
this.auth.authenticate().then((val)=>{
console.log(val);
}).catch(error=>{
//handle error
});
You just want to check or do you need to return it?
If it's only checking you can do this:
authenticate() {
this.storage.get('auth_token').then((val) => {
if(val){ ... } // or console.log it if it's just what you need.
}
}
If you need to return, create a promise like this:
authenticate = (): Promise<{exists: boolean, auth: any}> =>{
return new Promise<{exists: boolean, auth: any}>(res =>{
this.storage.get('auth_token').then((val) => {
if(val){
res({exists: true, auth: val});
} else {
res({exists: false, auth: val});
}
}
})
}
and later call authenticate().then(res =>{}) and access the object returned in res.
EDIT
As commented by Suraj and tested now, it doesn't need to be encapsulated inside a new promise, so if you need to return it just use the method Suraj suggested.