redux saga ajax call - not working as expected - ajax

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.

Related

using https.get instead of got causes a 308

this is a difficult question to ask because I am mystified, but let's see…
I am comparing Got with https.get, and have the following, bare simple code that works. Both Got and https.get return exactly the same result.
But when I use exactly the same code in my Fastify application, Got works as expected but https.get results in a 308.
Is there some way I can debug this code to see what is being sent out by https.get that is causing the remote server to respond with a 308 instead of 200?
import got from 'got';
import https from 'https';
const withGot = async (uri) => {
try {
const json = JSON.parse((await got(uri)).body);
console.log(json);
}
catch (error) {
console.error(error);
}
}
const withHttps = async (uri) => {
try {
const json = await getRequest(uri);
console.log(json);
}
catch (error) {
console.error(error);
}
}
const getRequest = async (uri) => {
return new Promise((resolve) => {
https.get(uri, (res) => {
const { statusCode } = res;
const contentType = res.headers['content-type'];
let error;
/**
* Any 2xx status code signals a successful response but
* here we're only checking for 200.
**/
if (statusCode !== 200) {
error = new Error(`ERROR\n${'-'.repeat(50)}\nRequest Failed.\nURI: ${uri}\nStatus Code: ${statusCode}`);
}
else if (!/^application\/json/.test(contentType)) {
error = new Error(`Invalid content-type.\nExpected application/json but received ${contentType}`);
}
if (error) {
console.error(error.message);
/**
* Consume response data to free up memory
**/
res.resume();
return;
}
res.setEncoding('utf8');
let rawData = '';
res.on('data', (chunk) => { rawData += chunk; });
res.on('end', () => {
try {
const parsedData = JSON.parse(rawData);
resolve(parsedData);
}
catch (e) {
console.error(e.message);
}
});
}).on('error', (e) => {
console.error(`Got error: ${e.message}`);
});
});
}
const uri = 'https://zenodo.org/api/records/?q=phylogeny';
withGot(uri);
withHttps(uri);
I figured out the reason for the problem (and the solution)… seems like when I use https.get, I still have to pass the options with a port 443 (the default port for https), otherwise, https seems to knock on port 80 and then gets redirected to port 443 which results in the server sending back html which causes the JSON parser to croak. If I pass an options object like below, then it works. But, it is still weird that the standalone script works fine without the options, so I continue to be mystified even though I have found a solution.
const options = {
hostname: 'zenodo.org',
port: 443,
path: `/api/records/?${qs}`,
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
};

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();

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

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

ReactJS rxjs obervable dom ajax, get request treated as an option method

I am using 2 seperate libraries to make an http get in my reactjs application as follows
import { ajax } from 'rxjs/observable/dom/ajax';
import { Observable } from 'rxjs/Observable';
import * as actionType from '../types';
import types from '../types';
export default (action$, store) => {
return action$.ofType(types.getIssuers.requested).mergeMap(action => {
return ajax({
url: 'http://127.0.0.1:8181/api/v1/status',
responseType: 'json',
method: 'GET',
timeout: 2000
}).map(xhr => {
console.log("Issuer Epic")
const jsonBody = xhr.response;
return {
type: types.getIssuers.completed,
payload: jsonBody,
};
}).catch(error => {
return Observable.of({ type: actionType.LOAD_CUSTOMER_ERROR });
});
});
};
var request = require('request');
request('http://127.0.0.1:8181/api/v1/status', function (error, response, body) {
console.log('error:', error);
console.log('statusCode:', response && response.statusCode);
console.log('body:', body);
});
I can retrieve data without an issue if um using request library
I need to use rxjs/observable/dom/ajax as per the project requirement but it seems my HTTP GET request turned out to be an HTTP OPTION. Is there a fix for this ?

How to handle Google OAuth flow via redux-saga

I am trying to implement Google OAuth 2 with with redux saga.
I have a watcher in my saga listening for GOOGLE_AUTH action which then executes googleLogin
function *watchGoogleAuth() {
yield *takeLatest(GOOGLE_AUTH, googleLogin)
}
function *googleLogin() {
const id_token = yield call(GoogleSignIn);
console.log(id_token);
const response = yield call(HttpHelper, 'google_token', 'POST', id_token, null);
console.log(response);
}
The implementation for GoogleSignIn is in apis.js
export function GoogleSignIn() {
const GoogleAuth = window.gapi.auth2.getAuthInstance();
GoogleAuth.signIn({scope: 'profile email'})
.then(
(res) => {
const GoogleUser = GoogleAuth.currentUser.get();
return {
id_token: GoogleUser.getAuthResponse().id_token
};
},
(err) => {
console.log(err)
}
)
}
But saga doesn't seem to wait for the GoogleSignIn to complete. As soon as OAuth consent screen pops up, saga proceeds executing the console.log without waiting for google signin promise to return actual data.
Is there any better way to handle this situation? Thanks!
To expand on #HenrikR's answer, the generator will not wait unless it receives a promise.
export const GoogleSignIn = () => {
const GoogleAuth = window.gapi.auth2.getAuthInstance();
return new Promise((resolve, reject) => {
GoogleAuth.signIn({scope: 'profile email'})
.then(
(res) => {
const GoogleUser = GoogleAuth.currentUser.get();
resolve(GoogleUser.getAuthResponse().id_token);
},
(err) => {
reject(err);
}
);
});
};
Accordingly, you should wrap the yield statement in a try/catch. Simplified and somewhat lazy:
function *googleLogin() {
try {
const id_token = yield call(GoogleSignIn);
if (id_token) { /* Possibly with more checks and validations */
console.log(id_token);
const response = yield call(HttpHelper, 'google_token', 'POST', id_token, null);
console.log(response);
}
} catch (e) {
console.log(e);
}
}

Resources