Why doesn't redux saga continue after fetch promise resolves? - react-redux

I have a request function that returns a promise. response.json(). My Issue is that although it resolves, but my loadData function does not continue past my call.
export function* loadData() {
console.log("Saga: Loading Data...");
try {
const response = yield call(request, requestURL,
{ method: 'GET', credentials: 'include', headers: {
'Content-Type': 'application/json', } });
// Nothing past this point is ever touched :(
if (response.success) {
console.log("SUCCESS!!!!");
...
export default function request(url, options) {
return fetch(url, options)
.then(checkStatus)
.then(parseJSON);
}
CheckStatus confirms a successful status on the data
function parseJSON(response) {
if (response.status === 204 || response.status === 205) {
return null;
}
var responseClone = response.clone();
console.log(responseClone.json());
return response.json();
}
When I run it, the console outputs the following. Confirming that my promise is resolving with the data I want. But the code still does not continue. Only does once every 50 or so tries.
SAGA: Loading Data...
[[PromiseStatus]]:"resolved"
[[PromiseValue]]:Object

Related

Await to AJAX call doen't work as expected

I have the following two functions:
function scan_request(address, file_url) {
$.ajax({
type: "POST",
async: true,
crossDomain: true,
url: 'http://some_site/api/file/scan',
dataType: "text",
success: function (data, textStatus, jqXHR) {
var json = $.parseJSON(data);//get json response and parse it
$(json).each(function (i, val) {//extract data from json
$.each(val, async function (key, value) {
if (key.toLowerCase() == "jobid") {
var result = await query_request();
alert("result:" + result);
}
});
});
}
});
}
async function query_request() {
var settings = {
"async": true,
"crossDomain": true,
"url": 'http://some_site/api/file/query',
"method": "POST"
}
var res;
$.ajax(settings).then(function (response) {
alert("response: " + response);
res = response;
});
return res;
}
It first alerts result: undefined
and after it alerts: response: [object Object]
But I'm expecting for:
First alert response: [object Object]
And after alert result: [object Object]
It seems like it doesnt wait to the call: var result = await query_request(); and therefore the result is undefined and the alert appears before the inner alert, what am I missing?
You can only await a promise. (Or a function that returns a promise.)
Your query_request() should return the promise that is created by $.ajax(). And since it does not need to await anything itself, it does not need to be marked as async.
// returns a promise, i.e. can be awaited in caller
function query_request(value) {
return $.ajax({
crossDomain: true,
url: 'http://some_site/api/file/query',
method: "POST",
data: {jobid: value}
});
}
Now you can await the result of query_request() inside an async function:
$(json).each(function (i, val) {
$.each(val, async function (key, value) {
if (key.toLowerCase() == "jobid") {
var result = await query_request(value);
alert("result:" + result);
}
});
});
However, this code has a problem - it dasiy-chains the requests inside the loop, when they all could actually be running in parallel. This means it's slower than it needs to be.
Shifting the approach a bit, we can make sure that the Ajax requests are running in parallel instead of one after another:
async function (data, textStatus, jqXHR) {
var todo = [], pending, results;
// make a list of all the things we want to request
$(json).each(async function (i, val) {
$.each(val, function (key, value) {
if (key.toLowerCase() == "jobid") todo.push(value);
});
});
// request them all in parallel (=> array of promises)
pending = todo.map(query_request);
// wait for all of the results
results = await Promise.all(pending)
// ...now work with the results
}

Cypress not waiting for Before block to complete

I am trying to achieve the following functionality
Before Block : Call the Cy.visit("/login") and call a Function which will trigger a REST API and process the REST API response and set the local storage.
Only after the local storage is set click on "My Account" Link
Here is the source Code I am trying.
import * as subscriberHelpers from '../../../helpers/subscriberHelpers';
import * as localStorage from '../../../helpers/localStorage';
describe('testCode', () => {
before((done) => {
cy.visit('/login', {
timeout: 10000,
onLoad: () => {
localStorage.write("CD-Environment", Cypress.env('defaultEnvironment'));
localStorage.write("CD-Language", "en-US");
localStorage.write("CD-SystemId", "85788485-e411-48a9-b478-610c1285dc1a");
}
})
subscriberHelpers.createSubscriber().then(()=>{
done();
})
})
it('sClick on my account link', () => {
cy.get('.c-header-listItem > .c-link').contains("My Account").click();
})
})
Here is the code to createSubscriber function
export function createSubscriber() {
let URL = `SOME URL`;
let body = {
Some Body
}
return new Promise((resolve, reject) => {
request.subscriberServiceRequest(URL, body).then((response) => {
if (response.status === 200 && ("SessionId" in response.body)) {
localStorage.write("CD-SessionId", response.body.SessionId);
localStorage.write("CD-SubscriberId", response.body.Subscriber.Id);
resolve();
}
else if (response.status === 200 && ("Fault" in response.body)) {
reject(response.body.Fault.Message);
}
})
})
}
Here is the code to subscriber Service request function
export function subscriberServiceRequest(url, body, headers = null) {
let defaultHeaders = { "CD-SystemId": "85788485-e411-48a9-b478-610c1285dc1a" }
if (headers != null) {
defaultHeaders = addHeaders(defaultHeaders, headers);
}
return new Cypress.Promise((resolve, reject) => {
cy.request({
url: url,
method: 'POST',
body: body,
headers: defaultHeaders
}).then((response) => {
resolve(response);
});
})
}
When I try Executing the code I am getting following error in cypress
But the element existing in the UI
Questions:
Why I am getting the error
How to call more than one async functions
in before block
How to tell cypress to wait till the functions on
before block get processed meaning not only wait till receiving the
response but wait till the response got processed in the THEN block
To answer your first question:
Why I am getting the error
.contains() specifically searches for elements within but not including the tag it is called on. In other words, someElement.contains("My Account") will not match someElement.
What you should have instead is this:
cy.get('.c-header-listItem').contains("My Account").click();
Or simply:
cy.contains("My Account").click();

react-native fetch with authorization header sometime return 401

I'm facing some issue whereby I sometime will get status code 401 (Unauthorised) from my phone. I'm trying to access to an API from my computer localhost (192.168.0.7).
I've a screen, when I click on a button it will navigate to a page and it will request data through API. And when I go back and navigate to same page again, it sometime will return me code 401.
So if I repeat the same step (navigate and go back) let's say 10 times. I'm getting Unauthorised like 5-7 times.
Below are my code
export function getMyCarpool(param,token) {
return dispatch => {
var requestUrl = _api + 'GetMyProduct?' + param;
fetch(requestUrl, {
method: "get",
headers: new Headers({
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer ' + token
})
})
.then((request) => {
console.log(request);
if(request.status == 200)
return request.json();
else if(request.status == 401) {
//dispatch(logout());
throw new Error('Unauthorized access.');
}
else
throw new Error('Failed to request, please try again.');
})
.then((response) => {
var message = response.message;
if(response.success == 'true')
dispatch({ message, type: GET_MY_PRODUCT_SUCCESS });
else
dispatch({ message, type: GET_MY_PRODUCT_FAILED });
})
.catch(error => {
var message = error.message;
dispatch({ message, type: GET_MY_PRODUCT_FAILED });
});
}
I've check the token in my phone and also trying to make many request using postman. So I don't think it's server side problem.
I'm using Laravel and using laravel passport for API authentication. I not sure why this happen if I continue to access many time, any help is greatly appreciated.
UPDATE :: I'm trying to capture whether the http request has the token from this link, and I don't get the problem anymore.
It's a healthy mechanism for token expire. Maybe you have your token (access_token) for 5 minutes, then the token expired, you should use refresh_token to regain another new token (access_token).
For code explanation:
async function fetchService(url) {
const reqSetting = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${Auth.access_token}`,
},
};
const prevRequest = { url, reqSetting };
const resp = await fetch(url, reqSetting);
if (!resp.ok) {
const error = new Error(resp.statusText || 'Request Failed!');
if (resp.status === 401 || resp.status === 400) {
const responseClone = resp.clone();
const errorInfo = await resp.json();
if (errorInfo.error == 'invalid_token') {
// console.log('Token Expired', errorInfo);
try {
await refreshToken();
const response = await fetchService(prevRequest.url);
return response;
} catch (err) {
// handle why not refresh a new token
}
}
return responseClone;
}
error.errorUrl = url;
error.code = resp.status;
throw error;
}
return resp;
}
Where the refresh token function is :
async function refreshToken() {
const url = 'https://example.com/oauth/token';
const data = {
grant_type: 'refresh_token',
refresh_token: Auth.refresh_token,
};
try {
const res = await fetch(url, data);
const data = res.json();
Auth.access_token = data.access_token;
Auth.refresh_token = data.refresh_token;
return true;
} catch (error) {
throw error;
}
}
This fetchService will automatic regain a new token if old expired and then handle old request.
PS.
If you have multiple requests same time, the fetchService will need a little optimization. You'd better choose another regain token strategy like saga.

redux saga ajax call - not working as expected

I have this redux saga code where everything works okay...until the promise, after that things start to go wrong
here's the relevant code
const firstApiRequest = ()=>{
return $.ajax({
url: myUrl,// ,
type:'POST',
headers: {
"Accept":"application/json",
"Content-Type":"application/json",
},
data:JSON.stringify(bodyData),
success:function(res){
console.log(res);
return res;
}
})
};
export function *startCheckout() {
try {
yield put(showLoading());
const data = yield call(firstApiRequest);//const data ends
yield put({type:FIRST_REQUEST_DONE,payload:data});
} catch (err) {
yield put(firstRequestFail(err));
}
}
export function *checkout() {
yield takeEvery(SEND_FIRST_REQUEST, startCheckout);
}
The problem is that after the return res in firstApiRequest , I wanted to use the data to send the FIRST_REQUEST_DONE action , but what happens is that the flow goes to FIRST_REQUEST_FAIL and shows error as true.
The problem is that the api response is coming back successfully and I am getting the data inside the error when the flow goes to FIRST_REQUEST_FAIL part of reducer and data shows up as error.
here's the code for reducer
where flow goes to
case 'FIRST_REQUEST_FAIL':
return {
loading: false,
error: true,
errorMessage: action.err,
};
instead of going to
case 'FIRST_REQUEST_DONE':
return {
id: action.id,
};
so, what's wrong with the code here? why does it show error even after a succesful response from server?
You shouldn't be defining the success in your api request.
$.ajax will return a promise on its own:
const firstApiRequest = () => (
$.ajax({
url: myUrl,// ,
type:'POST',
headers:{
"Accept":"application/json",
"Content-Type":"application/json",
},
data:JSON.stringify(bodyData),
}));
Also, why are you using jQuery for making the API requests? I'd suggest using axios or fetch
Here is an approach to handle API request using redux-saga:
First create a request helper
import 'whatwg-fetch';
function parseJSON(response) {
return response.json ? response.json() : response;
}
/**
* Checks if a network request came back fine, and throws an error if
not
*
* #param {object} response A response from a network request
*
* #return {object|undefined} Returns either the response, or throws an
* error
*/
function checkStatus(response, checkToken = true) {
if (response.status >= 200 && response.status < 300) {
return response;
}
return parseJSON(response).then(responseFormatted => {
const error = new Error(response.statusText);
error.response = response;
error.response.payload = responseFormatted;
throw error;
});
}
/**
* Requests a URL, returning a promise
*
* #param {string} url The URL we want to request
* #param {object} [options] The options we want to pass to "fetch"
*
* #return {object} The response data
*/
export default function request(url, options = {}) {
// Set headers
if (!options.headers) {
options.headers = Object.assign({
'Content-Type': 'application/json',
}, options.headers, {});
}
// Stringify body object
if (options && options.body) {
options.body = JSON.stringify(options.body);
}
return fetch(url, options)
.then(checkStatus)
.then(parseJSON)
}
In your saga
import { call, fork, put, takeLatest } from 'redux-saga/effects';
import request from 'utils/request';
import { submitSuccess, submitError } from './actions'; // path
to your actions.
import { SUBMIT } from './constants'; // The event you're listening
export function* submitData(action) {
try {
const response = yield call(request, 'your_url', { method: 'POST', body: action.body });
yield put(submitSuccess(response));
} catch(err) {
yield put(submitError(response.payload.message);
}
}
export function* defaultSaga() {
yield fork(takeLatest, SUBMIT, submitData);
}
export default defaultSaga;
Reducer
const initialState = fromJS({
submitSuccess: false,
submitReponse: '',
errorMessage: '',
});
function fooReducer(state = initialState, action) {
switch (action.type) {
case SUBMIT_SUCCESS:
return state
.update('submitSuccess', () => true)
.update('submitResponse', () => action.response);
case SUBMIT_ERROR:
return state.update('errorMessage', () => action.errorMessage);
//...
}
}
With this structure you should be able to catch your success and you error when you're making your request.

Parse.Com - HTTP method in cloud code, how do I wait for the response

In my parse cloud code, the HttpRequest in beforeSave is getting executed successfully but the code blows through before I have had time to parse the response and determine whether I want to return a response.success() or a response.error().
I know I am missing something here, any input, ideas from the community here would be appreciated. Thanks
Parse.Cloud.beforeSave(Parse.User, function (request, response) {
   var user = request.object;
    var key = user.get("recaptcha"); 
Parse.Cloud.httpRequest({
url: 'https://www.google.com/recaptcha/api/siteverify?secret=<ITS A SECRET>&response=' + key,
success: function (httpResponse) {
var status = JSON.parse(httpResponse.text).success;
console.log(status);
if (status === false) {
response.error();
} else {
response.success();
}
}
});
});
I got it working...Parse.Cloud.httpRequest() is asynchronous, here is the solution that worked for me, hope it helps someone else.
Parse.Cloud.beforeSave(Parse.User, function (request, response) {
var user = request.object;
var key = user.get("recaptcha");
if (!request.object.existed()) {
return Parse.Cloud.httpRequest({
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
url: 'https://www.google.com/recaptcha/api/siteverify?secret=<ITS A SECRET>&response=' + key,
body: request,
success: function(httpResponse) {
var status = JSON.parse(httpResponse.text).success;
if (status === false) {
response.error();
} else {
response.success();
}
},
error: function(httpResponse) {
response.error(httpResponse);
}
});
}
});

Resources