My use case is to map an Observable to redux actions of success and failure. I make a network call (with a function that gives promise), if it succeeds I have to forward a success action, if it fails than an error action. The Observable itself shall keep going. For all I could search, RxJS do not have a mechanism for this catching the error and retrying the original. I have following solution in my code which I am not happy with:
error$ = new Rx.Subject();
searchResultAction$ = search$
.flatMap(getSearchResultsPromise)
.map((resuls) => {
return {
type: 'SUCCESS_ACTION',
payload: {
results
}
}
})
.retryWhen((err$) => {
return err$
.pluck('query')
.do(error$.onNext.bind(error$));
});
searchErrorAction$
.map((query) => {
return {
type: 'ERROR_ACTION',
payload: {
query,
message: 'Error while retrieving data'
}
}
});
action$ = Observable
.merge(
searchResultAction$,
searchErrorAction$
)
.doOnError(err => console.error('Ignored error: ', err))
.retry();
action$.subscribe(dispatch);
i.e I create a subject, and push errors into that subject and create an Observable of error actions from that.
Is there a better alternative of doing this in RxJS that I am missing? Basically I want to emit a notification of what error has occurred, and then continue with whatever the Observable is already doing.
This would retry failed queries:
var action$ = search$
.flatMap(value => {
// create an observable that will execute
// the query each time it is subscribed
const query = Rx.Observable.defer(() => getSearchResultsPromise(value));
// add a retry operation to this query
return query.retryWhen(errors$ => errors$.do(err => {
console.log("ignoring error: ", err);
}));
})
.map(payload => ({ type: "SUCCESS_ACTION", payload }));
action$.subscribe(dispatcher);
If you don't want to retry, but just want to notify or ignore errors:
var action$ = search$
.flatMap(value => {
// create an observable that will execute
// the query each time it is subscribed
const query = Rx.Observable.defer(() => getSearchResultsPromise(value));
// add a catch clause to "ignore" the error
return query.catch(err => {
console.log("ignoring error: ", err);
return Observable.empty(); // no result for this query
}));
})
.map(payload => ({ type: "SUCCESS_ACTION", payload }));
action$.subscribe(dispatcher);
Related
I need to call an API that can return errors, warnings or success.
If it returns warnings the user must able to accept the warning and I should send the same payload + acceptWarning: true.
I need to display an ionic modal and wait for the user's response to see if he accepts or cancel the warning.
What should be the best way to achieve that?
Right now I have something like this:
#Effect()
public Assign$ = this.actions$.pipe(
ofType(myActions.Assign),
map(action => action.payload),
exhaustMap(assignment =>
this.assignService.assign(assignment).pipe(
switchMap(() => {
this.errorService.showPositiveToast(' Assigned Successfully');
return [
new LoadAssignments(),
new LoadOtherData()
];
}),
catchError(error =>
from(this.openErrorModal(error)).pipe(
switchMap(({ data = '' }) => {
if (data === 'Accept') {
return of(new Assign({ ...assignment, acceptWarning: true }));
}
return of(new AssignShipmentFailure());
})
)
)
)
)
);
async openErrorModal(response: any) {
const errorModal = await this.modalCtrl.create({
component: ErrorValidationPopup,
componentProps: {
response: response,
},
});
await errorModal.present();
return errorModal.onDidDismiss();
}
But it is not triggering the Assign action again. Thanks for your help
If any error occurred in the effect's observable (or any Observable), then its stream emitted no value and it immediately errored out. After the error, no completion occurred, and the Effect will stop working.
To keep the Effect working if any error occurred, you have to swichMap instead of exhaustMap, and handle the errors within the inner observable of the switchMap, so the main Observable won't be affected by that.
Why use switchMap?
The main difference between switchMap and other flattening operators is the cancelling effect. On each emission the previous inner observable (the result of the function you supplied) is cancelled and the new observable is subscribed. You can remember this by the phrase switch to a new observable
You can try something like the following:
#Effect()
public Assign$ = this.actions$.pipe(
ofType(myActions.Assign),
map(action => action.payload),
switchMap(assignment =>
this.assignService.assign(assignment).pipe(
switchMap(() => {
this.errorService.showPositiveToast('Assigned Successfully');
return [
new LoadAssignments(),
new LoadOtherData()
];
}),
catchError(error =>
from(this.openErrorModal(error)).pipe(
map(({ data = '' }) => {
if (data === 'Accept') {
return new Assign({ ...assignment, acceptWarning: true });
}
return new AssignShipmentFailure();
})
)
)
)
)
);
I want to call this api multiple times in my project and when I am calling it , It continues giving an error which is
TypeError: Failed to execute 'json' on 'Response': body stream already
read at main.js:Line number
My Code is as Follows
let thisIsUrl = 'https://api.covid19api.com/summary';
let a = fetch(thisIsUrl)
a.then((data) => {
return data.json()
}).then((apidata) => {
console.log(apidata)
return apidata
}).catch((error) => {
console.log(error)
})
a.then((fetchdata) => {
return fetchdata.json()
}).then((readingData) => {
console.log(readingData)
}).catch((err) => {
console.log(err)
})
You're not calling fetch multiple times. You're calling it once, and then trying to read the response body multiple times. That's why the error says you're trying to read the body when the stream is already closed — it was closed when you were done reading it the first time.
If you want to use the data twice, store it somewhere and use it twice.
let thisIsUrl = 'https://api.covid19api.com/summary';
let a = fetch(thisIsUrl)
a.then((data) => {
return data.json()
}).then((apidata) => {
// **************** Use it twice here
}).catch((error) => {
console.log(error)
})
If you want to fetch it again because it may have been updated, call fetch again:
let thisIsUrl = 'https://api.covid19api.com/summary';
fetch(thisIsUrl)
.then((data) => {
return data.json();
}).then((apidata) => {
console.log(apidata);
return apidata;
}).catch((error) => {
console.log(error);
});
// Presumably this is later (in time), not immediately after the above
fetch(thisIsUrl)
.then((fetchdata) => {
return fetchdata.json();
}).then((readingData) => {
console.log(readingData);
}).catch((err) => {
console.log(err);
});
Finally, this seems unlikely, but if you really want to fetch it once and use that one result in multiple places via the promise chain, keep the promise from then rather than the promise from fetch:
let thisIsUrl = 'https://api.covid19api.com/summary';
let a = fetch(thisIsUrl)
.then((data) => {
return data.json()
});
a.then((apidata) => {
// ***** Use it here
}).catch((error) => {
console.log(error)
})
a.then((readingData) => {
// ***** And then again here
}).catch((err) => {
console.log(err);
});
Side note: Your code is falling prey to a footgun in the fetch API; I've written about it in this blog post. fetch only rejects its promise on network errors, not HTTP errors. You have to check for those yourself in the first fulfillment handler, by checking for ok on the response object:
fetch("/your/resource")
.then(response => {
if (!response.ok) {
throw new Error("HTTP error " + response.status); // Or better, use an Error subclass
}
return response.json();
})
// ...
fetch returns Promise, generally, promises have something like state inside themself;
pending: initial state, neither fulfilled nor rejected.
fulfilled: meaning that the operation was completed successfully.
rejected: meaning that the operation failed.
(source)
So when we call them and get the value from them with then, catch and etc. then they change the state after that call. So here, when you read the value with a.then(…, the promise changes its state to fulfilled and you are not able to call it again, you need a new and fresh Promise, actually a new instance of the fetch.
I want to recommend you to use Promise.all().
let thisIsUrl = 'https://api.covid19api.com/summary';
let a = fetch(thisIsUrl)
.then((data) => {
return data.json()
}).then((apidata) => {
console.log(apidata)
return apidata
}).catch((error) => {
console.log(error)
})
Promise.all([a,a,a]);
.then(results => {
// Results are here.
});
what should be the best approach to manage Redux-Thunk async requests.
like I'm posting some data on server and the connected component will check the errors by
Approach 1: I just return new Promise in my action creators to check resolve or reject by using then
const update = (todoId, isDone) => (dispatch) =>
new Promise(function(resolve, reject) {
dispatch({
type: 'SET_SAVING',
saving: true
});
// Function is expected to return a promise
callUpdateApi(todoId, isDone).then(updatedTodo => {
dispatch({
type: 'SET_SAVING',
saving: false
});
resolve(updatedTodo);
}).catch(error => {
// TBD: Handle errors for Redux
reject(error);
})
});
Approach 2: using dispatch to manage error in render method by if-else conditions
const update = (todoId, isDone) => (dispatch) => {
dispatch({
type: 'SET_SAVING',
saving: true
});
// Function is expected to return a promise
callUpdateApi(todoId, isDone).then(updatedTodo => {
dispatch({
type: 'SET_SAVING',
saving: false
});
});
// TBD: Handle errors
}
please help me find the best solution for this
should I go with "return Promise" from Action creators or just using the dispatch actions to store for error and success handling always.
because onsuccess I need to do some stuff in my component and on error also
const update = (todoId, isDone) => (dispatch) =>
new Promise(function(resolve, reject) {
dispatch({
type: 'SET_SAVING',
saving: true
});
// Function is expected to return a promise
callUpdateApi(todoId, isDone).then(updatedTodo => {
dispatch({
type: 'SET_SAVING',
saving: false
});
resolve(updatedTodo);
}).catch(error => {
// TBD: Handle errors for Redux
reject(error);
})
});
If your callUpdateApi returns a promise you don't have to wrap your whole action in a promise, just can just return callUpdateApi. As for error handling, the common way is to set a flag somewhere in your redux state along with the saving flag for example to know when an error occured. Your components will then receive those flags and do something with them
const update = (todoId, isDone) => (dispatch) => {
dispatch({
type: 'SET_SAVING',
saving: true
});
return callUpdateToApi(todoId, isDone).then(updatedTodo => {
dispatch({
type: 'SET_SAVING',
saving: false,
// Do something with your API response
// e.g. update your redux store via a reducer
updatedTodo
});
})
.catch(err => {
// Handle error, for example set a error flag
// in your redux state so your components know an error occured
dispatch({
type: 'SET_SAVING',
saving: false,
error: true
});
});
}
Connect your component so that they can access error and saving flags and for example display an error when your call failed:
export default connect(
state => ({
error: state.module.error,
saving: state.module.saving
})
)(Component);
// Inside your JSX
{this.props.error && <p>An error occured</p>}
the best practice we use is each thunk action dispatched 3 actions:
action started
action succeeded
action failed
callUpdateApi is a promise then just return it in your thunk like this:
const update = (params) => (dispatch) => {
dispatch(started())
return callUpdateApi(params)
.then(result => dispatch(succeeded(result)))
.catch(error => dispatch(failed(error)))
}
and inside the reducer you can toggle the saving flag for started set it to true and for succeeded or failed set it to false
that's it.
I'm working with React/Redux in a project and I need to make 2 separate API requests, but the second request depends on the first returning without any issues. In the below example of an action, I'm trying to wrap both calls in a promise, but it's not quite working (getting a Uncaught Error: Actions must be plain objects. Use custom middleware for async actions. error in the console). I don't necessarily need to do anything with the response data as a result of both calls. I just need them to return a 200 status or an error.
Note: Unfortunately, I can't use async/await for this example. Thanks!
export default () => {
const url = '/api';
const options = {...}
const otherOptions = {...}
return new Promise((resolve, reject) => {
return dispatch =>
// First API request
axios.post(url, options)
.then(responseA => dispatch({ type: RESPONSE_A_SUCCESS }))
.then(() =>
// Second separate API request
axios.post(url, otherOptions)
.then(responseB => {
dispatch({ type: RESPONSE_B_SUCCESS });
resolve();
})
)
.catch(error => {
dispatch({ type: errorActionType, error: error });
reject(error);
});
});
};
Your code has 2 issues:
It returns a promise, which is not a "plain object".
You are nesting promises instead of attaching them sequentally
Try this instead:
export default () => {
const url = '/api';
const options = {...}
const otherOptions = {...}
return dispatch =>
axios.post(url, options)
.then(responseA => dispatch({ type: RESPONSE_A_SUCCESS }))
.then(() => axios.post(url, otherOptions))
.then(responseB => dispatch({ type: RESPONSE_B_SUCCESS }))
.catch(error => {
dispatch({ type: errorActionType, error: error });
reject(error);
});
});
};
I have a scenario where I have to get the request payload passed when the service fails so I can return back along with error response. My code goes like below.
#Effect() doGetEvents$: Observable<Action> = this.actions$
.ofType(EVENTS)
.switchMap((action) => {
let eventDate = action.payload.date;
return this.http.service(action.payload);
})
.map(res => {
// success
if (res.status) {
return CustomActions.prototype.eventsResponse({ type: EVENTS_RESPONSE, payload: res.payload });
}
//failure
return CustomActions.prototype.EventsErrorResponse({
type: CustomActions.EVENTS_ERROR_RESPONSE,
payload: {
status: res.status,
errorMessage: res.errorMessage,
billDate: '10/01/2016', // <--- I need the eventDate got from the above switchMap
errorType: CustomActions.EVENTS + '_ERROR'
}
});
});
I tried passing like
.switchMap((action) => {
let eventDate = action.payload.date;
return [eventDate, this.http.service(action.payload)];
})
but this won't execute the http call and won't return the response on .map() args.
Also the are options to make the eventDate outside the scope of Effects and assign it when service fails but it is not a cleaner approach, there should be some way passing data round not sure what I missed!
If you want to include information from the payload, along with the HTTP service's result, you can use the map operator, like this:
.switchMap((action) => {
return this.http
.service(action.payload)
.map(result => [action.payload.date, result]);
})
.map(([date, result]) => { ... })