IF in Redux Observable epic - rxjs

I have an epic that catch each dispatch of getting status (just item from state, like state.process:{ status: fail, success, inWork}, not an request status like 200, 500 etc).
When status == success (by getting status from state) i need to dispatch another action like SET_STATUS_SUCCESS
const getStatus = (action, state) =>
action.pipe(
ofType(GET_STATUS),
withLatestFrom(state),
mergeMap(([action, state]) => {
const { status } = state.api.process; //here is what i need, there is no problem with status.
if (status === "success") {
return mapTo(SET_STATUS_SUCCESS) //got nothing and error.
}
})
);
Now i receive error:
Uncaught TypeError: You provided 'function (source) { return
source.lift(new MapToOperator(value)); }' where a stream was expected.
You can provide an Observable, Promise, Array, or Iterable.
at subscribeTo (subscribeTo.js:41)
What should i do? I tried just return setStatusSuccess action but it doesn't work too.

You need to return an observable from the function you pass to mergeMap. Try this:
const getStatus = (action, state) =>
action.pipe(
ofType(GET_STATUS),
withLatestFrom(state),
mergeMap(([action, state]) => {
const { status } = state.api.process;
if (status === 'success') {
return of({ type: SET_STATUS_SUCCESS });
} else {
return EMPTY;
}
}),
);
of and EMPTY are imported from rxjs.

Related

how to catch error of an asyn function call

I don't succeed catching the error that a async function returns. Using Vuejs 3 with a pinia store, althought I don't think that this is specific to vue or pinia.
In a pinia store I have this function:
const getAccount = async(id, month, year) => {
try {
getData.defaults.headers.common['__authorization__'] = UserStore.jwt
const response = await getData.get(`/use/b/comptes/${id}/${month}/${year}`)
if (response.status === 200) {
// update store state and:
return true
}
return false
} catch (error) {
// => this gets correctly executed when the server responds with 409
// console.error(`erreur catch : ${error.response.data.error}`)
return error.response.data
}
}
I'm calling this function from a component, like so:
watchEffect(() => {
if (route.name === 'comptes') {
getAccount( compte.value.id, route.params.month, route.params.year )
.then(result => { console.log('result', result.error) })
.catch(err => {
// this never gets executed, including when server returns a 409
console.log('err', err)
})
}
})
In other words: in the function call, only the then block gets executed, not the catch.
How do I catch the error of the first function?

Debounce in redux-tool-kit

I'm Trying to debounce below API call with using lodash debounce
export const getProfile = createAsyncThunk(
GET_PROFILE,
async (amount: any, { rejectWithValue }: any) => {
try {
const response = await API.Get(EndPoint.GET_PROFILE)
console.log(response)
return response.data
} catch (error: any) {
amount.failCallBack(error?.response?.data?.msg || 'something_went_wrong')
return rejectWithValue(error?.code || 'Something went wrong..!')
}
}
)
above function is worked without any errors and fetch data able to see inside fullfilled of the action
so i tried to implement debounce as below way
export const getProfile = createAsyncThunk(
GET_PROFILE,
debounce(async (amount: any, { rejectWithValue }: any) => {
try {
const response = await API.Get(EndPoint.GET_PROFILE)
console.log(response)
return response.data
} catch (error: any) {
amount.failCallBack(error?.response?.data?.msg || 'something_went_wrong')
return rejectWithValue(error?.code || 'Something went wrong..!')
}
}, 5000)
)
Now there is no any exceptions in web app and when i console log the fullfilled action it shows
payload as undefined
{
"type": "login/getProfile/fulfilled",
"meta": {
"arg": {
"data": "login"
},
payload: undefined,
"requestId": "8pfalpIzFl8nNOgi2jRcb",
"requestStatus": "fulfilled"
}
}
any suggestions for fix this issue.
thanks in advance
Don't debounce the payload creator - debounce dispatching the thunk action. And since you probably don't want to that in your component, do it in a manual thunk
const getProfile = createAsyncThunk( ... normal definition ... );
const debounced = debounce((arg, dispatch) => dispatch(getProfile(arg)), 5000);
const debouncedGetProfile = (arg) => (dispatch) => debounced(arg, dispatch)
and then use that
dispatch(debouncedGetProfile(amount))

Axios handle error using interceptor (js promises)

I am using an interceptor in axios to check for errors.
service.interceptors.response.use(
response => response,
error => {
const originalRequest = error.config
if (!error.response) {
// No network connectivity
}
}
)
My request looks like this
service.post('endpoint').then(response => {
// Successful request
}).catch(error => {
// Handle error
})
In case there is any error such as an unsuccessful status code (e. g. 400), I wan't to handle the error in the catch part of the second code example. However, if there is a network issue, I wan't to handle the error in the first code example. In that case neither then, nor catch of the second code example should be called. How can I achieve this?
When you have a promise chain already in place you can't stop the flow:
const axios = require('axios')
axios.interceptors.response.use(
response => response,
manageErrorConnection
)
// here you have created a promise chain that can't be changed:
axios.get('http://localhost:3000/user?ID=12345')
.then(handleResponse)
.catch(handleError)
function manageErrorConnection(err) {
if (err.response && err.response.status >= 400 && err.response.status <= 500) {
// this will trigger the `handleError` function in the promise chain
return Promise.reject(new Error('Bad status code'))
} else if (err.code === 'ECONNREFUSED') {
// this will trigger the `handlerResponse` function in the promise chain
// bacause we are not returning a rejection! Just an example
return 'nevermind'
} else {
// this will trigger the `handleError` function in the promise chain
return Promise.reject(err)
}
}
function handleResponse(response) {
console.log(`handleResponse: ${response}`);
}
function handleError(error) {
console.log(`handleError: ${error}`);
}
So in order to run an optional step, you need to:
put logic in the handler, to skip it
put the handler in the chain when it is needed (I would just avoid that since it is "spaghetti software")
put the logic in the handler example
// .... changing this line ...
return Promise.reject('nevermind')
// ....
function handleError(error) {
if (error === 'nevermind') {
return
}
console.log(`handleError: ${error}`);
}
This logic could be isolated:
axios.get('http://google.it/user?ID=12345')
.then(handleResponse)
.catch(shouldHandleError)
.catch(handleError)
function manageErrorConnection(err) { return Promise.reject('nevermind') }
function handleResponse(response) { console.log(`handleResponse: ${response}`); }
function shouldHandleError(error) {
if (error === 'nevermind') {
// this stop the chain
console.log('avoid handling');
return
}
return Promise.reject(error)
}
function handleError(error) { console.log(`handleError: ${error}`); }

Keeping error information and the outer observable alive

To ensure an error doesn't complete the outer observable, a common rxjs effects pattern I've adopted is:
public saySomething$: Observable<Action> = createEffect(() => {
return this.actions.pipe(
ofType<AppActions.SaySomething>(AppActions.SAY_SOMETHING),
// Switch to the result of the inner observable.
switchMap((action) => {
// This service could fail.
return this.service.saySomething(action.payload).pipe(
// Return `null` to keep the outer observable alive!
catchError((error) => {
// What can I do with error here?
return of(null);
})
)
}),
// The result could be null because something could go wrong.
tap((result: Result | null) => {
if (result) {
// Do something with the result!
}
}),
// Update the store state.
map((result: Result | null) => {
if (result) {
return new AppActions.SaySomethingSuccess(result);
}
// It would be nice if I had access the **error** here.
return new AppActions.SaySomethingFail();
}));
});
Notice that I'm using catchError on the inner observable to keep the outer observable alive if the underlying network call fails (service.saySomething(action.payload)):
catchError((error) => {
// What can I do with error here?
return of(null);
})
The subsequent tap and map operators accommodate this in their signatures by allowing null, i.e. (result: Result | null). However, I lose the error information. Ultimately when the final map method returns new AppActions.SaySomethingFail(); I have lost any information about the error.
How can I keep the error information throughout the pipe rather than losing it at the point it's caught?
As suggested in comments you should use Type guard function
Unfortunately I can't run typescript in snippet so I commented types
const { of, throwError, operators: {
switchMap,
tap,
map,
catchError
}
} = rxjs;
const actions = of({payload: 'data'});
const service = {
saySomething: () => throwError(new Error('test'))
}
const AppActions = {
}
AppActions.SaySomethingSuccess = function () {
}
AppActions.SaySomethingFail = function() {
}
/* Type guard */
function isError(value/*: Result | Error*/)/* value is Error*/ {
return value instanceof Error;
}
const observable = actions.pipe(
switchMap((action) => {
return service.saySomething(action.payload).pipe(
catchError((error) => {
return of(error);
})
)
}),
tap((result/*: Result | Error*/) => {
if (isError(result)) {
console.log('tap error')
return;
}
console.log('tap result');
}),
map((result/*: Result | Error*/) => {
if (isError(result)) {
console.log('map error')
return new AppActions.SaySomethingFail();
}
console.log('map result');
return new AppActions.SaySomethingSuccess(result);
}));
observable.subscribe(_ => {
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js"></script>
I wouldn't try to keep the error information throughout the pipe. Instead you should separate your success pipeline (tap, map) from your error pipeline (catchError) by adding all operators to the observable whose result they should actually work with, i.e. your inner observable.
public saySomething$: Observable<Action> = createEffect(() => {
return this.actions.pipe(
ofType<AppActions.SaySomething>(AppActions.SAY_SOMETHING),
switchMap((action) => this.service.saySomething(action.payload).pipe(
tap((result: Result) => {
// Do something with the result!
}),
// Update the store state.
map((result: Result) => {
return new AppActions.SaySomethingSuccess(result);
}),
catchError((error) => {
// I can access the **error** here.
return of(new AppActions.SaySomethingFail());
})
)),
);
});
This way tap and map will only be executed on success results from this.service.saySomething. Move all your error side effects and error mapping into catchError.

Which RxJS operator to choose to handle HTTP errors: tap or catchError?

/* error handler that will be used below in pipe with catchError()
* when resource fetched with HttpClient get() */
private _handleError<T> (operation: string, result?:T) {
return( error: any): Observable<T> => {
console.error( operation + ' ' + error.message );
// or something else I want to do
return of(result as T); // lets me return innocuous results
}
}
getObjects() {
return this.http.get<any[]>(this.myUrl).pipe(
catchError(this._handleError('my error', [])
);
}
now using tap to handle errors
getObjects() {
return this.http.get<any[]>(this.myUrl).pipe(
tap( objects => {
// whatever action like logging a message for instance
}, err => {
console.error(err);
// whatever else I want to do
})
);
}
Why should I choose one approach instead of the other? Will handling HTTP errors with tap() keep my app' running in case they occur?
tap is to cause side effects.
catchError is to catch errors in a stream and try to handle them.
Therefore if you want to handle errors of http requests use catchError.
http.get('https://test.com/').pipe(
tap({
next: () => {
// 200, awesome!, no errors will trigger it.
},
error: () => {
// error is here, but we can only call side things.
},
}),
catchError(
(error: HttpErrorResponse): Observable<any> => {
// we expect 404, it's not a failure for us.
if (error.status === 404) {
return of(null); // or any other stream like of('') etc.
}
// other errors we don't know how to handle and throw them further.
return throwError(error);
},
),
).subscribe(
response => {
// 200 triggers it with proper response.
// 404 triggers it with null. `tap` can't make 404 valid again.
},
error => {
// any error except 404 will be here.
},
);

Resources