Really odd issue I am having. For some reason my put in my catch block is not being executed below. Here's my saga:
function* postLoginFormSaga(action) {
let response;
try {
// Data from emitting login action
response = yield call(AuthenticationApi.login, action.formData);
yield put(createLoginSucceedAction());
yield put(push('/dashboard/summary'));
} catch (e) {
console.log(e);
yield put(createLoginFailAction(response))
}
}
And my api call, which has a custom middleware to handle non 2XX responses:
static login = (formData) => {
return fetch('/api/login', {
method: 'POST',
body: formData,
credentials: 'same-origin'
}).then(handleFetchErrorMiddleWare)
.then(r => r.json())
};
Where the middleware function is a simple function that checks the ok property of the response object:
export function handleFetchErrorMiddleWare(response) {
if (!response.ok){
throw Error(response.status)
}
return response
}
The middleware is working correctly as I can see the output of the console.log(e) when an exception is thrown, but the yield put(createLoginFailAction(response)) is not being emitted ...
See below:
Any ideas? Thanks
Seems to me like there is an error in the createLoginFailAction function, if so that would cause the result that you are seeing.
Related
I'm trying to call API using common HTTPSERVICE method, Strangely I'm getting response whenever the debugger is connected, but API is throwing Network Error when debugger is not connected.
React-Native Version: 0.63.2
axios verison: 0.18.0
const sendRequest = async (url, method, params, data={}, headers = {}) => {
try {
const options = {
url,
method,
params,
data,
headers,
}
console.log(options);
return await axios(options);
} catch (err) {
console.log("request error",err);
throw err;
}
}
function* HttpService(url, methodType, params, data, headerParams = {}) {
try {
const user = yield select(state => state.ssoReducer.user);
if (isTokenExpired(user.token)) {
yield call(getPingFedToken);
}
const signIn = yield select(state => state.ssoReducer);
const authToken = signIn.user?.token;
const headers = {
Authorization: `${authToken}`,
'SWAP-MOBILE-APP': true,
"Content-Type": "application/x-www-form-urlencoded",
...headerParams
};
return yield call(sendRequest, url, methodType, params, data , headers)
} catch (error) {
console.log("http error",error);
if (error.response.status === (403)) {
yield put({
type: SSO_FAILURE,
payload: "Unauthorised alert"
});
}
throw error;
}
}
export default HttpService;
enter image description here
From what I see, the problem is not in this part of the code.
I am suspecting the way you create headerParams.
here is what you need to do.
check all the global methods that you are using in this flow ex. URLSearchParams
make sure that they exist in the web component layer that is part of the JS engine related to the react-native version you are using.
why:
because, when you connect the debugger; you are not executing your JS code in the engine of React Native, but inside the one in the chrome you are debugging with.
this is a known issue in the react-native world, and to avoid it I suggest you use another debugger like https://infinite.red/reactotron
Using fetch API and async/await, is it possible to continue polling indefinitely, regardless of availability of a URL? I anticipate that a URL might become available eventually, so I want to keep trying until a condition is met. Tried to come up with a minimum viable code sample and I'm not sure I pulled it off:
// this is just a placeholder. It will eventually be a function
// that evaluates something real.
// Assume validContinue gets updated elsewhere.
function shouldContinue() {
return validContinue;
}
async function wonderPoll(someUrl) {
// just a delay mechanism
function wait(ms = 1000) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
// the actual individual poll
async function pollingFunction(url) {
const response = await fetch(url, {
cache: 'no-store'
});
if (response.ok) {
return response;
} else {
Promise.reject(response);
}
}
// allegedly keep polling until condition is met.
// But the rejected Promise is breaking out!
while (shouldContinue()) {
await wait();
result = await pollingFunction(someUrl);
}
// when the fetch hits a rejected state, we never get here!
console.log('done with the while loop, returning last successful result')
return result;
}
const sampleUrl = 'https://get.geojs.io/v1/ip/country.json?ip=8.8.8.8';
const sampleUrl2 = 'http://totallybroken_fo_sho';
// swap the URL to test
wonderPoll(sampleUrl)
.then((result) => {
console.log('got a result', result)
})
.catch((err) => {
console.log('got an error', err)
});
I see what's happening (I think). The parent call ultimately executes the polling function, which rejects on the Promise. The condition to continue is still theoretically met, but the rejection breaks out of the While loop and sends to rejection directly up. This propagates all the way up to the catch method of the original/initial Promise. It doesn't even hit any code that would have come after the While loop in the case of resolved Promises.
What I don't know is how to prevent that from happening. I think I don't understand the syntax for intercepting and resolving the promise. When I replace Promise.reject in the response parser with Promise.resolve(response), it still ends up rejecting up to the top.
If the URL I provide is valid, it will continue until the condition is no longer met.
Here's a fiddle: https://jsfiddle.net/gregpettit/qf495bjm/5/
To use the fiddle, the "stop" button simulates the condition being met, and I've provided two different URLs that have to be manually swapped (by passing someUrl or someUrl2) to test.
Expected results:
with good URL, continuous polling (will have to dig into network in dev tools) until condition is met (by pressing Stop!) and then the calling function's 'then' can show the result.
with bad URL, continuous polling until condition is met, and then calling function's 'catch' shows the error
Actual results:
positive test case is OK
negative test case goes directly to the catch
You can try…catch it to prevent breaking out of loop.
while (shouldContinue()) {
try {
await wait();
result = await pollingFunction(someUrl);
} catch (e) {}
}
Change the code in while loop to try/catch so you can catch the error
result can hold a value when there's no error, or a reason when there is an error
Once the loop is stopped, you either return the value, or throw with the reason
As below
async function wonderPoll(someUrl) {
// just a delay mechanism
function wait(ms = 1000) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
// the actual individual poll
async function pollingFunction(url) {
const response = await fetch(url, {
cache: 'no-store'
});
if (response.ok) {
return response;
} else {
Promise.reject(response);
}
}
// allegedly keep polling until condition is met. But the rejected Promise is breaking out!
while (shouldContinue()) {
try {
await wait();
const value = await pollingFunction(someUrl);
result = {value};
} catch (reason) {
result = {reason};
}
}
// when the fetch hits a rejected state, we never get here!
console.log('done with the while loop, returning last successful result')
if (result.reason) {
throw result.reason;
}
return result.value;
}
Running example https://jsfiddle.net/twkbo9pg/
the example includes status in the result, but that is unnecessary (I borrowed code from my Promise.allSettled polyfill and forgot to remove that property)
you might want to check out observable streams! If you're going to have a lot of data coming in over time, that's rxjs's whole thing.
There's actually a few ways to do this if this feels janky (it kinda does haha).
import { ajax } from "rxjs/ajax";
import { duration } from "moment-timezone"; // I copied this from some old code... whatever.
import { catchError, map, share, switchMap } from "rxjs/operators";
const baseUrl = "http://foo.bar"
const base = (method, headers = {}) => ({
method,
headers: {
Accept: "application/json",
...headers,
},
crossDomain: true,
withCredentials: true,
})
const ajaxGet = url => ajax({ ...base("GET"), url })
export const userEpic = timer(0, duration(5, "minutes").asMilliseconds()).pipe(
switchMap(() =>
ajaxGet(`${baseUrl}/users`).pipe(
map(({ response }) => getUsersSuccess(response)),
catchError(e => of(getUsersError(e))),
)
),
share()
)
Two things
} else {
Promise.reject(response);
}
should return that. It's working "by accident" right now.
} else {
return Promise.reject(response);
}
Secondly, result = await pollingFunction(someUrl); might want to add .catch to it:
result = await pollingFunction(someUrl).catch(_=>null); or whatever can be tested for in the enclosing while
But I think you can simplify the whole thing thus:
export async function wonderPoll(someUrl) {
while (shouldContinue()) {
await wait();
const response = await fetch(someUrl, { cache: 'no-store' });
if (response.ok)
return response;
}
return Promise.reject(); // only if !shouldContinue()
}
So I am implementing axios call cancelation in the project. Right now looking at axios documentation it seems pretty straight forward https://github.com/axios/axios#cancellation
So I did define variables on the top of my Vue component like
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
obviously on top of that is import axios from 'axios';
Then I have a method of fetching the API
On the top of the method I want to cancel out the request in case it is running so the last one cancels out if the user spams the filtering.
async fetchPartners(inputToClear) {
source.cancel();
...
try {
const response = await axios.get(`../partners?limit=1000${this.createRequestString()}`, {
cancelToken: source.token
});
// Here you can see I did add the cancelToken to the request
this.partners = response.data.data;
} catch (error) {
if (axios.isCancel(error)) {
console.log('Request canceled', error.message);
}
const fetchErrors = this.utilGlobalHandleErrorMessages(error);
this.utilGlobalDisplayMessage(fetchErrors.message, { type: 'error' });
return [];
} finally {
...
}
},
So it is pretty straight forward, just took the code from axios documentation I gave you above, it should be working by logic. But what is actually happening, it doesn't even allow me to fetch the call, it is already cancelled out before I can call it. On console it shows me
Request canceled undefined
It just catches the error as if I am cancelling the call, but how can it be, because I am source.cancel() before the call.
Anyone has any idea?
I hope you should throttle your requests instead of canceling the request.
Could you please try the following if throttle does not suit your requirement?
const CancelToken = axios.CancelToken;
let source;
async fetchPartners(inputToClear) {
if(source){
source.cancel();
}
...
source = CancelToken.source();
try {
const response = await axios.get(`../partners?limit=1000${this.createRequestString()}`, {
cancelToken: source.token
});
// Here you can see I did add the cancelToken to the request
this.partners = response.data.data;
} catch (error) {
if (axios.isCancel(error)) {
console.log('Request canceled', error.message);
}
const fetchErrors = this.utilGlobalHandleErrorMessages(error);
this.utilGlobalDisplayMessage(fetchErrors.message, {
type: 'error'
});
return [];
} finally {
...
}
}
In axios, why throw new Error() is not allowed inside catch()?
I am in such requirement, where if an error is returned by the server, the catch block should throw an error, which later on will be handled by redux-saga and appropriate action would be dispatched.
The API call:
export const userSignupApi = userDetails => {
const endpoint = `${URL_ROOT}${URN_SIGNUP}`;
axios
.post(endpoint, userDetails)
.then(response => {
console.log("Response: ", response);
//do something with the response
})
.catch(error => {
throw new Error(error.response.data.message);
});
};
I am getting Unhandled Rejection (Error) because of the above catch block.
Below is my saga which handles the operation:
import { call, takeLatest, put } from "redux-saga/effects";
function* handleUserSignup(action) {
try {
yield call(userSignupApi, action.userDetails);
yield put(userSignupSuccess()); //dispatching success action
} catch (error) {
yield put(userSignupFail(error)); //dispatching error action
}
}
function* watchUserSignup() {
yield takeLatest(NEW_USER.SIGNUP, handleUserSignup);
}
Edit: Why I want the above code structure? Because this makes easy to unit test the API code and the saga code.
The Promise created in userSignupAPI isn't being used anywhere (it's not even being returned), so when an error is thrown inside catch, the Promise chain resolves to an (uncaught) rejected Promise, resulting in the error.
In the caller of userSignupAPI, you should await the call, so that the try/catch inside handleUserSignup will see the thrown error:
export const userSignupAPI = userDetails => {
const endpoint = `${URL_ROOT}${URN_SIGNUP}`;
return axios
.post(endpoint, userDetails)
.then(response => {
console.log("Response: ", response);
})
.catch(error => {
throw new Error(error.response.data.message);
});
};
async function* handleUserSignup(action) {
try {
yield await call(userSignupApi, action.userDetails);
yield put(userSignupSuccess()); //dispatching success action
} catch (error) {
yield put(userSignupFail(error)); //dispatching error action
}
}
function* watchUserSignup() {
yield takeLatest(NEW_USER.SIGNUP, handleUserSignup);
}
(make sure that call returns the Promise returned by userSignupApi)
I got this working. I was doing it completely wrong. As suggested by #certianPerformance and after reading some questions and github issues, I got the right way to handle this. Instead of returning the api response, I should have returned the promise.
Here is the solution:
export const userSignupApi = userDetails => {
const endpoint = `${URL_ROOT}${URN_SIGNUP}`;
return axios.post(endpoint, userDetails);
};
Saga:
import { call, takeLatest, put } from "redux-saga/effects";
function* handleUserSignup(action) {
try {
const response = yield call(userSignupApi, action.userDetails);
response.then(response => {
const location = response.headers.location;
put(userSignupSuccess(location));
put(newUserWelcomeNote("Welcome user"));
});
} catch (error) {
const errorMessage = error.response.data.message;
yield put(userSignupFail(errorMessage));
}
}
function* watchUserSignup() {
yield takeLatest(NEW_USER.SIGNUP, handleUserSignup);
}
You are using try catch because you don't want to throw error in browser console you want to handle in catch. Throwing error in catch will remove its purpose.If you want to throw error remove try catch(which is not a recommended way)
UPDATE
For axios catch method catches any error thrown by api url. If you don't want to catch error and show it in browser, you can remove catch block or you can call the action which handles error. For more information about promise catch you can refer here
When everything goes well it returns an object with status: 'OK'
If something goes wrong it returns status: 'FAILED',
Due to this approach I need to handle errors in both the try and catch blocks, e.g.
try {
const {data} = await axios.post(....);
if(data.status === 'OK') {
// do something and return
}
//handle error
} catch(e) {
// handle Error Again
}
As you can see I need to handle the errors two times. I am using redux-saga so I make another file for APIs and handle it in saga like this:
yield = ...my api
// then
if (data.status === 'OK') {
// do something and return
}
and so on... My end goal is to keep my sagas clean. Is there any way in which I can call the API then check the status in the API file? It should go to the catch block of the saga so I would have to handle the errors in my catch block only?
My end goal would be like this
function* doSomethng() {
try {
yield callapi...
//do something
} catch(e) {
// handle errors and even if the api status is 200
// but it returns an object with status 'FAILED' it should come here
}
}
You can create a wrapper function, that will add a check to the promise chain and throw on bad status.
const checkStatus = async (promise) => {
const result = await promise;
if (!result || result.status === 'FAILED') throw new Error('Request failed');
return result;
}
function* doSomething() {
try {
yield checkStatus(callapi(...));
//do something
} catch(e) {
// ...
}
}
You can also "hid"e the checkStatus call in the callapi function itself.