I have a method in my component that uses observable to listen to first event and has a timeout of 15s. The method calls a nested observable that should throw an error if the parameter is null or empty. I use Observable.throw() but the error only propagates after the timeout/15s.
this.signUp(this.user)
.first()
.timeout(15000)
.subscribe((authResponse) => {
console.log("next fired");
dialogs.alert("Next: " + authResponse);
}, (error) => {
//fired only after 15s when mobile is empty
console.log("error fired");
console.log(JSON.stringify(error));
}, () => {
console.log("completed fired");
});
signUp()
public signUp(user: User): Observable<AuthResponse> {
let observable = new Subject<AuthResponse>();
this.isUserExisting(user.mobile)
.first()
.subscribe((isUserExisting) => {
if (isUserExisting) {
console.log("User already exists");
observable.next(AuthResponse.USER_EXISTING);
} else {
console.log("User does not exist");
this.saveUser(user).first().subscribe(() => {
observable.next(AuthResponse.SUCCESS);
})
}
}, (error) => {
return Observable.throw(error);
})
return observable;
}
public isUserExisting(mobile: string): Observable<boolean> {
let observable = new Subject<boolean>();
if (!mobile) {
console.log("empty mobile");
return Observable.throw(new Error("Mobile number cannot be empty"));
}
firebase.query(() => { }, "/users",
{
singleEvent: true,
orderBy: {
type: firebase.QueryOrderByType.CHILD,
value: "mobile"
},
range: {
type: firebase.QueryRangeType.EQUAL_TO,
value: mobile
}
}
).then((result) => {
console.log("Checking for user success: ");
console.log(JSON.stringify(result));
observable.next(result.value != null);
});
return observable;
}
Update:
saveUser()
public saveUser(user: User) {
return Observable.defer(() => firebase.push('/users', user)
.then((result) => {
console.log("Created user record with key: " + result.key);
console.log("Dumping result:");
console.log(JSON.stringify(result));
})
.catch((error) => {
console.log("Error while saving user: " + error);
})
)
}
It is a bad practice to create your own subject on which you will emit observable values which have been emitted by an internal subscribed observable. This leads to memory leaks because you have no way to unsubscribe from your internal subscription.
public signUp(user: User): Observable<AuthResponse> {
return this.isUserExisting(user.mobile)
.flatMap(isExistingUser => {
if (isExistingUser) {
console.log('user already exists: ' + isUserExisting);
return Rx.Obserable.of(AuthResponse.USER_EXISTING);
}
return saveUser(user).map(result => AuthResponse.SUCCESS)
})
.catch(err => {
console.log(`error during signup of user: ${user.mobile})`);
return Rx.Observable.of(AuthResponse.FAILED_TO_SIGNUP);
})
.first();
}
Without the subscription in your signUp function your code can unsubscribe from it and errors are propagated without any work on your side. This is also the reason your timeout is hit instead of your mobile number check; you forgot to propagate the error. Instead you tried to return the Rx.Observable.throw from within your subscribeOnError callback, which has a signature of onError(error):void
A good practice is to keep your Rx functions lazy; only execute the code inside when somebody subscribes to it. This helps in reducing hard to trace errors. In your case using the firebase.query() (which returns a promise) you can use .defer() to wait with executing the promise until somebody subscribes to your isUserExisting function.
public isUserExisting(mobile: string): Observable<boolean> {
if (!mobile) {
console.log("empty mobile");
return Observable.throw(new Error("Mobile number cannot be empty"));
}
return Rx.Observable.defer(() => firebase.query(() => { }, "/users",
{
singleEvent: true,
orderBy: {
type: firebase.QueryOrderByType.CHILD,
value: "mobile"
},
range: {
type: firebase.QueryRangeType.EQUAL_TO,
value: mobile
}
}
))
.do(firebaseResult => console.log('Checking for user success: ' + JSON.stringify(firebaseResult)))
.filter(firebaseResult => firebaseResult.value != null);
}
public saveUser(user: User) {
return Observable.defer(() => firebase.push('/users', user))
.do(result => console.log(`created user with key ${result.key}`))
}
Related
I've had a hard time figuring out how Promise.reject() returns its value.
const propertyData = {
id: 1,
propertyName: '1BR Japanese-style Private Room near Kyoto Station'
}
function handleClick(e) {
getData()
.then(data => {
console.log(data.id, data.propertyName); // -> 1, '1BR Japanese-style Private Room near Kyoto Station'
})
.catch(err => {
console.log(err.message); // -> 'Failed to fetch data.' if it's only 'err', it returns the object.
})
}
function getData() {
return fetchData()
.then(result => {
if(result.success){
return Promise.resolve(result.propertyData);
} else {
return Promise.reject(result.message);
}
})
}
function fetchData() {
return new Promise((resolve, reject) => {
let rand = Math.floor(Math.random()*11)
setTimeout(() => {
if(rand >= 3 && rand <= 10){
resolve({ success: true, propertyData: propertyData });
} else {
reject({ success: false, message: 'Failed to fetch data.' });
}
}, 1000)
})
}
First, I expected err in the error handling in the function handleClick would be the message saying 'Failed to fetch data' because Promise.reject() in the function getData() returns reject.message.
However, it seems to return the object { success: false, message: 'Failed to fetch data.' }.
That is why it needs to set err.message in the function handleClick in order to get the message string.
Does this prove that Promise.reject() always returns object even if you set the return value like result.message?
If so, Promise.resolve() looks behave differently.
It returns result.propertyData, so it doesn't need to set as data.propertyData[key] in the function handleClick unlike the error handling.
Do they, Promise.resolve() and Promise.reject() returns its value differently? or are there something that I missed?
I hope this explains well to describe my confusion.
Thank you.
const propertyData = {
id: 1,
propertyName: "1BR Japanese-style Private Room near Kyoto Station",
};
function handleClick(e) {
fetchData()
.then((data) => {
// We are resolving Object with properties
// 1. success
// 2. propertyData (declared on line 1)
// e.g. Accessing those
// - data.success OR - data.propertyData
const { id, propertyName } = data.propertyData; // Simple destruction of data.propertyData;
console.log(id, propertyName);
})
.catch((err) => {
console.log(err.message); // -> 'Failed to fetch data.' if it's only 'err', it returns the object.
});
}
function fetchData() {
return new Promise((resolve, reject) => {
let rand = Math.floor(Math.random() * 11);
setTimeout(() => {
if (rand >= 3 && rand <= 10) {
//{ This is what will be the resolved data in .then(data => ) }
// It can be anything you want string, Object, boolean w/e
resolve({ success: true, propertyData });
} else {
//{ This is what will be the resolved value in .catch()
// Does not necessarily have to be Object
// But if you pass only string then in your .catch(err => ) : err here will be string
// In your case err will be Object with props 'success' and 'message'
// To access them u go err.message OR err.success
reject({ success: false, message: "Failed to fetch data." });
}
}, 1000);
});
}
I would like to take a reponse from post metho and use this value in subscribe method.
In comments I wrote also information about my question.
My code
login(): void {
this.authService.login(this.model)
.subscribe(next => {
//here I wanted to use received value
this.alertifyService.success('success');
}, error => {
this.alertifyService.error('error');
}
);
}
and
login(model: any) {
return this.http.post(this.baseUrl + 'login', model)
.pipe(map((response: any) => {
// how to send this reponse to my subscribe method ?
const user = response;
if (user) {
localStorage.setItem('token', user.token);
this.decodedToken = this.jwtHelper.decodeToken(user.token);
console.log(this.decodedToken);
}
}
));
}
you just have to return the value from map
login(model: any) {
return this.http.post(this.baseUrl + 'login', model)
.pipe(map((response: any) => {
const user = response;
if (user) {
localStorage.setItem('token', user.token);
this.decodedToken = this.jwtHelper.decodeToken(user.token);
return this.decodedToken;
}
}
));
}
and then use it like this:
authService.login(/**/).subscribe(token => /*do something with the token*/)
Is there any reason why I'm being signed out before my firebase actions are done finishing?
What Should Happen:
I first make a post request to my server to update values in my db.
Then I update the users firebase email if its changed.
Then I update the email if its changed as well.
Then finally I want to sign the user out.
What Happens:
I instantly gets signed out and then get errors in my console because the other actions couldn't be completed.
I have tried to trail the .then() after my axios post as well but I still had the same issue of being instantly signed out.
export const updateUserData = (userData) => {
return (dispatch, getState, {getFirebase}) => {
const state = getState();
const firebase = getFirebase()
let user = firebase.auth().currentUser;
let cred = firebase.auth.EmailAuthProvider.credential(user.email, userData.oldPassword);
user.reauthenticateWithCredential(cred).then(() => {
axios.post('/updateUserData', {
uid: state.firebase.auth.uid,
email: userData.email,
firstName: userData.firstName,
lastName: userData.lastName,
devices: userData.devices,
}, {
headers: {
"Authorization": `${state.firebase.auth.stsTokenManager.accessToken}`,
'Content-Type': 'application/json',
},
withCredentials: true
}).catch(err => {
console.log("Failed Email Change: " + err)
});
}).then(() => {
if (state.firebase.auth.email !== userData.email) {
firebase.auth().currentUser.updateEmail(userData.email).then(() => {
console.log("Email Changed")
}).catch(err => {
console.log("Failed Email Change: " + err)
});
}
}).then(() => {
if (userData.newPassword.length !== 0) {
firebase.auth().currentUser.updatePassword(userData.newPassword).then(() => {
console.log("Successful Password Change")
}).catch(err => {
console.log("Failed Password Change: " + err)
});
}
}).then(() => {
firebase.auth().signOut().then(null)
})
}
}
You aren't returning values from your promise chains. If you want an async action to take place after another one when using Promises, you need to return them:
// don't do this
doThing().then(() => {
doSomethingElse().then(() => { /* ... */ });
}).then(() => {
// this will happen before doSomethingElse is finished
finallyDoThing();
});
// instead do this
doThing().then(() => {
return doSomethingElse();
}).then(() => {
return finallyDoThing();
});
So I have a http get function defined in a Service class that gets an object from an endpoint and does some processing on the result:
public get(url: string, params: {}): Observable<Object> {
return this.http.get<IResult>(this.endpointRootUrl() + url, {
params: params
}).pipe(
map(res => {
if (res.Result !== 0)
throw new Error(res.Message)
else
return res.Object;
}),
catchError((err: HttpErrorResponse) => throwError(err.statusText))
)
}
This function is called from an ExampleService which gets the observable and does some more processing on it:
public loadData(): Observable<IData[]> {
return this.get("/DataLink/ListData", {}).pipe(
map(res => { return <IData[]>res }),
catchError((err: string) => throwError("There was an error retrieving data: " + err))
)
}
My questions are:
In the http get function where I check the Result property of the IResult object that gets returned from the back-end, I throw an error if the value of the result is not what is expected.
The issue is, the Message property does not get properly sent into the catchError part of the loadData function; the error message returns as "There was an error retrieving data: undefined". What am I doing wrong here?
Is this code an acceptable way to achieve what I'm trying to do? I'm open to suggestions/critique.
Thanks in advance for any help offered.
Try to move catchError before all another pipes, exept takeUntil
e.g.
Inside component:
return this.apiService.changeStatus(id, status)
.pipe(
catchError((error: HttpErrorResponse) => {
this.showMsg('error', error.error.message);
return throwError(error);
}),
tap((data: SomeResponseModel) => {
this.status = data;
})
);
Inside service
changeStatus(id: number, status: StatusesEnum): Observable<SomeModel> {
const payload: { status: StatusesEnum} = { status };
return this.http.patch<ResponseModel<SomeModel>>(this.apiUrl(`/${id}/status`), payload)
.pipe(map((data: ResponseModel<SomeModel>) => new SomeModel(data.data)));
}
In the http get function where I check the Result property of the
IResult object that gets returned from the back-end, I throw an error
if the value of the result is not what is expected. The issue is, the
Message property does not get properly sent into the catchError part
of the loadData function; the error message returns as "There was an
error retrieving data: undefined". What am I doing wrong here?
Error object doesnt have statusText property
I think you are looking for message property
catchError((err: Error) => throwError(err.message))
With that change rest of code works fine.
Example
onst data = {
Message: "Random error",
Result: 1,
Object: {
test: 1
}
}
function get(data) {
return rxjs.of(data).pipe(
rxjs.operators.map(res => {
if (res.Result !== 0) {
throw new Error(res.Message)
} else {
return res.Object;
}
}),
rxjs.operators.catchError((err) => {
return rxjs.throwError(err.message)
})
)
}
function loadData(data) {
return get(data).pipe(
rxjs.operators.catchError(err => {
return rxjs.throwError(`There was an error retrieving data: "${err}"`)
})
)
}
loadData(data).subscribe({
next: (value) => {
console.log(value)
},
error: (error) => {
console.log('error ', error)
},
complete: () => {
console.log('completed ')
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js"></script>
I have two Redux actions which need to execute synchronously. requestStripeToken is called in my component (signupComponent.js), but in order to get a Stripe Token I first need to make a call to an internal API to get the current Stripe Key (as this changes depending on the environment and SKU). Both of these functions are set up as separate actions in my actions file (actions.js).
The issue I have it that I am not sure how to consume the requestStripeToken function in my component. I don't know whether it is an issue with what I am returning in the requestStripeToken action or whether the Promise consumption logic in my component needs to change. Note I am using redux-thunk middleware.
// actions.js
export function requestStripeToken(values) {
return function(dispatch) {
const { cardNumber, cvc, nameOnCard, expiryMonth, expiryYear, billingLine1, billingLine2, billingCity, billingState, billingZip, billingCountry } = values;
// We need to get the Stripe key before we can request a Stripe Token
return dispatch(getStripeSecretKey())
// Curried function necessary as getStripeSecretKey returns the fetch Promise inside of function(dispatch) ?
.then(() => (key) => {
console.log(key);
return new Promise((resolve, reject) => {
Stripe.setPublishableKey(key);
Stripe.card.createToken({
number: cardNumber,
cvc,
name: nameOnCard,
exp_month: expiryMonth,
exp_year: expiryYear,
address_line1: billingLine1,
address_line2: billingLine2,
address_city: billingCity,
address_state: billingState,
address_zip: billingZip,
address_country: billingCountry,
}, (status, response) => {
if (response.error) {
dispatch(addNotification({
message: response.error.message,
level: `error`,
autoDismiss: 0,
}));
reject();
}
return resolve(response.id);
});
});
});
};
}
export function getStripeSecretKey() {
return function(dispatch) {
return fetch(`${getAPIPath}api/stripeKey`, {
method: `GET`,
credentials: `include`,
headers: {
Accept: `application/json`,
},
})
.then(handleErrors)
.then((response) => {
response.json().then((res) => {
return res.data;
});
})
.catch(response => response.json().then((res) => {
dispatch(addNotification({
message: res.message,
level: `error`,
autoDismiss: 0,
}));
throw res;
}));
};
}
console.log(key) in this file never gets called.
// signupComponent.js
handleCreateAccountSubmit = (values) => {
this.setState({ submitting: true });
// We need the Stripe Token before we can signup the user so needs to be synchronous
this.props.actions.requestStripeToken(values)
.then((stripeToken) => {
console.log(stripeToken);
this.signupUser(values, stripeToken);
})
.catch(() => this.stopSubmission());
}
console.log(stripeToken) in this file returns:
ƒ (key) {
console.log(key);
return new Promise(function (resolve, reject) {
Stripe.setPublishableKey(key);
Stripe.card.createToken({
number: cardNumber,
…
You need to return Promises in your getStripeSecretKey() as well.
Dispatch returns what the action creator returns, so if you do:
export function getStripeSecretKey() {
return function(dispatch) {
return fetch(${getAPIPath}api/stripeKey, {
method:GET,
credentials:include,
headers: {
Accept:application/json,
},
})
.then(handleErrors) // also return Promise.reject() in errors
.then((response) => {
return response.json().then((res) => { // DONT BREAK RETURN CHAIN
return Promise.resolve(res.data); // RESOLVE
});
})
.catch(response => response.json().then((res) => {
dispatch(addNotification({
message: res.message,
level:error,
autoDismiss: 0,
}));
return Promise.reject(res); // REJECT
}));
};
}