How would you automatically retry a failed ajax call 3 times (synchronously)? - javascript

I'm trying to rerun a failed AJAX call 3 times. After the third attempt, I'd like to call a failed method. I don't want the AJAX calls to over run each other though.
What's the safest/best way to achieve this with what I'm working with?
I'm using a globalAjaxRequest method like so:
globalAjaxRequest(request, successCallback, errorCallback) {
let ajaxRequest = null;
if (request.url) {
const ajaxOptions = {
type: request.method ? request.method.toUpperCase() : 'POST',
url: request.url,
data: request.data || undefined,
beforeSend: request.beforeSend,
success: (data) => {
successCallback(data);
},
error: (data) => {
if (errorCallback) {
errorCallback(data);
}
}
};
ajaxOptions.dataType = request.dataType || 'json';
ajaxOptions.contentType = request.contentType || 'application/json; charset=utf-8';
if (request.contentType) {
ajaxOptions.data = $.parseJSON(JSON.stringify(ajaxOptions.data));
} else {
ajaxOptions.data = JSON.stringify(ajaxOptions.data);
}
ajaxRequest = $.ajax(ajaxOptions);
}
return ajaxRequest;
}
}
Here's my attempt:
callAPI() {
const callData = {
url: '/callApi',
data: {
id: 'something'
}
};
global.Utils.globalAjaxRequest(callData, (success) => {
console.log('success');
successMethod();
}, (fail) => {
for (let i = 1;; i++) {
i <= 3 && setTimeout(() => {
callAPI();
}, 1000);
if (i > 3) {
failedMethod();
break;
}
}
});
}
callAPI();

You can't retry an asynchronous operation such as $.ajax() synchronously, so I'll assume that you just meant you want to automatically retry sequentially if it fails.
Here's a generic retry function for $.ajax():
// general purpose promise delay, useful when you want to delay a promise chain
function pDelay(t, v) {
return new Promise(function(resolve) {
setTimeout(resolve, t, v);
});
}
// three arguments:
// options: value for $.ajax(options) - does not support other forms of calling $.ajax()
// delay: amount of time in ms to delay before each retry (can be 0 if you want)
// retries: number of times to retry, defaults to 3 if you don't pass it
$.ajaxRetry = function(options, delay, retries) {
// default value for retries is 3 if the argument is not passed
let retriesRemaining = retriesRemaining !== undefined ? retriesRemaining: 3;
let opts = Object.assign({}, options);
function run() {
return $.ajax(opts).catch(function(err) {
--retriesRemaining;
// don't fire this more than once
delete opts.beforeSend;
if (retriesRemaining > 0) {
// try again after short delay
return pDelay(delay).then(run);
} else {
// hit max retries, propagate error back to caller
throw e;
}
});
}
return run();
}
FYI, this code assumes that "failure" in your case means that the promise that $.ajax() rejects. If "failure" means something else (such as looking at some result you got), then you will have to insert that additional test into the retry loop or expose a callback where that additional test can be provided externally.
To integrate this into your wrapper, you could do this:
globalAjaxRequest(request, successCallback, errorCallback) {
let ajaxRequest = null;
if (request.url) {
const ajaxOptions = {
type: request.method ? request.method.toUpperCase() : 'POST',
url: request.url,
data: request.data || undefined,
beforeSend: request.beforeSend,
};
ajaxOptions.dataType = request.dataType || 'json';
ajaxOptions.contentType = request.contentType || 'application/json; charset=utf-8';
if (request.contentType) {
ajaxOptions.data = $.parseJSON(JSON.stringify(ajaxOptions.data));
} else {
ajaxOptions.data = JSON.stringify(ajaxOptions.data);
}
errorCallback = errorCallback || function(err) { throw err; };
ajaxRequest = $.ajaxRetry(ajaxOptions, 0, 3).then(successCallback, errorCallback);
}
return ajaxRequest;
}
}
FYI, it is kind of odd to take a promise interface and turn it back into plain callbacks. It seems you should just get rid of successCallback and errorCallback let the caller use the returned promise.

I'd do something like this that uses a closure to keep a counter above the async request:
globalAjaxRequest(request, successCallback, errorCallback, maxRequests) {
maxRequests = maxRequests || 1;
var requests = 1;
function ajaxRequest(request){
if (request.url) {
const ajaxOptions = {
type: request.method ? request.method.toUpperCase() : 'POST',
url: request.url,
data: request.data || undefined,
beforeSend: request.beforeSend,
success: (data) => {
successCallback(data);
},
error: (data) => {
if (requests < maxRequests){
requests++;
ajaxRequest(request);
} else if (errorCallback) {
errorCallback(data);
}
}
};
ajaxOptions.dataType = request.dataType || 'json';
ajaxOptions.contentType = request.contentType || 'application/json; charset=utf-8';
if (request.contentType) {
ajaxOptions.data = $.parseJSON(JSON.stringify(ajaxOptions.data));
} else {
ajaxOptions.data = JSON.stringify(ajaxOptions.data);
}
return $.ajax(ajaxOptions)
}
ajaxRequest(request);
}

Related

axios-retry is not working for response with status 4XX

I am using axios for api calls.
Incase some failure happens or response status != 200 I need to retry the api call.
By default retry axios works for status with 5XX . But as per documentation we can override retryCondition as per our requirements.
Here is my code snippet
export const doApiFetchCall = (apiEndPoint, dataPayLoad, config, axiosObject, callType,caller,timeoutParam,retryCount) => {
let instance = undefined;
if(axiosObject === 'axios') {
instance = localAxios;
} else if(axiosObject === 'axiosProxy') {
instance = localAxiosProxy;
} else if (axiosObject === 'axiosProxyJira') {
instance = localAxiosProxyJira;
}
let restOptions = {
url: apiEndPoint,
method: callType,
timeout: timeoutParam || 20000, // timeout in ms
headers:config.headers||null,
raxConfig: {
retry: retryCount || 0, // number of retry when facing 4xx or 5xx
instance: instance,
retryCondition: () => true,
onRetryAttempt: err => {
let tempError = Object.assign({}, err)//{...err}
const cfg = rax.getConfig(err);
delete tempError.config;
delete tempError.request;
},
noResponseRetries: 3, // number of retry when facing connection error
httpMethodsToRetry: ['GET', 'HEAD', 'OPTIONS', 'DELETE', 'PUT', 'POST', 'PATCH'],
retryDelay: 3000,
backoffType: 'static'
}
};
var caller_id = caller||'';
if (dataPayLoad) restOptions = {...restOptions, data: dataPayLoad};
return new Promise((resolve, reject) => {
instance(restOptions)
.then(response => {
logger.info(caller_id, '[API_CALL_SUCCESS] API call has succeeded');
if (response) {
const {status, data} = response;
logger.info(caller_id, '[API_CALL_SUCCESS] API call Status Code: [' + status + ']');
try {
if (status === 200 || status === 201) {
resolve(response);
} else {
reject(null);
}
} catch (jsonParseError) {
reject(jsonParseError);
}
} else {
resolve(null);
}
})
.catch(error => {
const {response, request, message, config} = error;
reject(error);
});
I have overridden retryCondition I am not sure if its done in correct way.
Can someone please let me know what wrong I am doing ?
Got the fix .
I over ride statusCodesToRetry property.

Await is not working inside a method javascript

I have an ajax call which return some data. Code is:
function ProductCodeLookUp() {
return $.ajax({
url: "/ProductInventory/GetProductCodeHirerchy",
type: "get", //send it through get method
async: false,
data: {
productCode: $('#ProductCodeText_Id').val()
},
success: function (response) {
debugger;
if (response != null && (response.tissueClassificationType != null || response.tissueClassificationType != null)) {
$('#productCodeTable_id').show();
$('#ProductCodeNotExistsWarning_id').hide();
//addOptionsToSelect(response, 'size_id', $('input[id=size_id]').val());
$('#ProductCodeTextClassification_Id').text(response.tissueClassificationType);
$('#ProductCodeTextTissueType_Id').text(response.tissueType);
$('#ProductCodeTextProduct_Id').text(response.tissueDefinition);
$('#ProductCodeTextSize_Id').text(response.tissueDefinitionSize);
}
else {
$('#ProductCodeNotExistsWarning_id').show();
$('#productCodeTable_id').hide();
}
},
error: function (xhr) {
//Do Something to handle error
}
});
}
I Have to take some decision on the basis of the data returned by this Ajax call. The method where i am calling ProductCodeLookUp() method is:
async function checkRequiredFields() {
var isValid = false;
if (checkRequiredFieldsSubMethod() == true) {
if (AddProductInBulk() == true) {
if (QualityChecksValidation() == true) {
debugger;
var res = await ProductCodeLookUp();
if (res.tissueDefinitionSize != null)
isValid = true;
else
isValid = false;
}
}
}
debugger;
return isValid;
}
Issue is that it does not await on the ProductCodeLookUp() method. I have to wait for the result of this method and on the basis of result make a decision that whether to return true or false. But i am unable to do this. What can be the possible issue.
Wrap the ajax call in a promise that resolves on success and rejects on error.
function ProductCodeLookUp() {
return new Promise((resolve, reject) => $.ajax({
// ...
success: function (response) {
// ...
resolve();
},
error: function (response, status, err) {
// ...
reject(err);
}
}));
}
Also, set async to true

How can I use async GM_xmlhttpRequest to return values in original order?

I'm trying to make a Tampermonkey script to update dates on some site.
I got an array of id's from a site, and I'm requesting data from it with the id of the array. After that, I have to return data of each Input.
As the function is async, it returns data in a random order, but I need those new arrays to return in the original order. I have tried sync and Promises, but the first is too slow and I haven't understood the second.
I can sort ids, but I also got the dates which are in the order of the first Array, so I don't know how to achieve the same order as the second id array.
Here's the code:
id = GM_getValue('id');
for (let i = 0; i < id.length; i++) {
setTimeout(() => {
console.log("Updating " + (i + 1) + " Title");
GM_xmlhttpRequest({
method: "GET",
url: "***" + id[i] + "/***",
onload: function(response) {
$(response.responseText).find("#main-form :input").each(function(x) {
if (x == 0) ids.push(parseInt($(this).val()));
if (x == 1) array.push($(this).val()));
});
}
});
}, i * 333);
}
You can use Promises to execute the GET requests in a specific order. Here's an example:
id = GM_getValue('id');
function makeGetRequest(url) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: url,
onload: function(response) {
resolve(response.responseText);
},
onerror: function(error) {
reject(error);
}
});
});
}
for (let i = 0; i < id.length; i++) {
console.log("Updating " + (i + 1) + " Title");
try {
const response = await makeGetRequest("***" + id[i] + "/***");
$(response).find("#main-form :input").each(function(x) {
if (x == 0) ids.push(parseInt($(this).val()));
if (x == 1) array.push($(this).val());
});
} catch (error) { // in case the GET request fails
console.error("Request failed with error code", error.status, ". Message is ", error.responseText);
}
}
In this example, I've created a makeGetRequest() function with returns a promise, which is resolved on GET success, but rejected on failure.
await waits for the Promise to settle before moving on and the try exists to catch Promise rejection (if the GET fails).
References:
Promise on MDN.
await on MDN.
TypeScript:
export enum HttpDataType {
JSON = "JSON"
}
import {HttpDataType} from "./enum/HttpDataType";
export default class Http {
static get(option: { url: string, dataType?: HttpDataType, synchronous?: boolean, onload?: Function }) {
option['method'] = 'GET';
if (option.synchronous) {
return new Promise((resolve, reject) => {
// #ts-ignore
GM_xmlhttpRequest({
...option,
onload: (response) => {
resolve(option.dataType === HttpDataType.JSON ? JSON.parse(response.responseText) : response.responseText);
},
onerror: (error) => {
reject(error);
}
});
})
} else {
const onload1 = function (details) {
let response;
if (option.dataType === HttpDataType.JSON) {
response = JSON.parse(details.responseText);
} else {
response = details.response;
}
option.onload(response);
}
// #ts-ignore
GM_xmlhttpRequest({...option, onload: onload1});
}
}
}
static async getXxx() {
let response = await Http.get({
url: '……',
dataType: HttpDataType.JSON,
synchronous: true
});
// #ts-ignore
if (!response || response.status !== 'success') {
console.error('getXxx error', response);
}
// #ts-ignore
return response.data;
}
this.getXxx().then((data: any) => {
// ……
});

JavaScript Function ReEntrant in promise object

I would like to reentrant function in promise object.
this function contains Asynchronous processing.
however, this function dose NOT Work.
To be specified, DOES NOT fired ,next "then method".
the code is here
loopcount = 0;
getItemcount = 0;
global_ItemCol = [];
function GetItem_in_List_Over5000(parentSiteUrl, listGuid)
{
if (loopcount == 0) {
console.log("Enter FirstTime");
endPoint = parentSiteUrl + "/_api/Web/Lists(guid'" + listGuid + "')/Items?$top=3000&$select=Title,Id,ContentTypeId,HasUniqueRoleAssignments";
} else {
console.log("Eneter SecondTime");
}
return new Promise(function (resolve_GetItem_in_List5000, reject_GetItem_in_List5000) {
console.log("Eneter Inner Function");
$.ajax({
type: 'GET',
url: endPoint,
headers: { 'accept': 'application/json;odata=verbose', "X-RequestDigest": $("#__REQUESTDIGEST").val() },
success: function (data) {
console.log(data.d.__next);
if (data.d.__next) {
global_ItemCol = global_ItemCol.concat(data.d.results);
endPoint = data.d.__next;
loopcount++;
console.log("looopcount increment. " + global_ItemCol.length);
GetItem_in_List_Over5000(parentSiteUrl, listGuid);
} else {
global_ItemCol = global_ItemCol.concat(data.d.results);
var local_col = [];
local_col = local_col.concat(global_ItemCol);
loopcount = 0;
global_ItemCol.length = 0;
resolve_GetItem_in_List5000(local_col);
console.log("return call");
//return Promise.resolve().then(local_col);
resolve_GetItem_in_List5000(local_col);
}
},
error: function (error) {
OutputLog(error.responseJSON.error.message.value);
loopcount = 0;
reject_GetItem_in_List5000();
}
});
});
}
I called this function Added Array and Promise.All().
Thanks in advance.
You could try a recursive function. Store results in an array (not global but pass it to the recursive function). With every result set store the guid so you know what result set came from what guid (when requests start failing you know what you've done so far).
function GetItem_in_List_Over5000(parentSiteUrl, listGuid) {
const recur = (listGuid,results=[]) =>
$.ajax({
type: 'GET',
url: parentSiteUrl + "/_api/Web/Lists(guid'" + listGuid + "')/Items?$top=3000&$select=Title,Id,ContentTypeId,HasUniqueRoleAssignments",
headers: { 'accept': 'application/json;odata=verbose', "X-RequestDigest": $("#__REQUESTDIGEST").val() },
}).then(
function (data) {
console.log(data.d.__next);
if (data.d.__next) {
return recur(
data.d.__next,
results.concat([listGuid,data.d.results])
);
} else {
//add listGuid to result set so you know where it came from
return results.concat([listGuid,data.d.results]);
}
}
).fail(//newer jQuery can use .catch
err=>({type:"error",error:err,results:results})
);
return recur(listGuid)
}
GetItem_in_List_Over5000("url","guid")
.then(
results=>{
if((results&&results.type)===error){
console.log("something went wrong:",results.error);
console.log("have some results:",results.results);
}else{
console.log("got all results:",results);
}
}
)

React-native NetInfo with promises

I have a modified code in react-native for fetching data with server, that works fine. I want to add NetInfo to always check before fetching if telephone has connection to internet. Is it posible inside promise? How to connect this async function to my code?
'use strict';
var MAX_WAITING_TIME = 30000
var processStatus = function (response) {
// status "0" to handle local files fetching (e.g. Cordova/Phonegap etc.)
if (response.status === 200 || response.status === 0 || response.status === 201 || response.status === 422 || response.status === 302 ) {
return Promise.resolve(response)
} else if(response.status === 413) {
return Promise.reject(alert(____mobile.connection_error.large_file))
} else {
//return Promise.reject(alert("Process status: "+JSON.stringify(response )))
return Promise.reject(alert(____mobile.connection_error.top));
console.log("Process status: "+JSON.stringify(response ));
}
};
var parseJson = function (response) {
return response.json();
};
var getWrappedPromise = function () {
var wrappedPromise = {},
promise = new Promise(function (resolve, reject) {
wrappedPromise.resolve = resolve;
wrappedPromise.reject = reject;
});
wrappedPromise.then = promise.then.bind(promise);
wrappedPromise.catch = promise.catch.bind(promise);
wrappedPromise.promise = promise;// e.g. if you want to provide somewhere only promise, without .resolve/.reject/.catch methods
return wrappedPromise;
};
/* #returns {wrapped Promise} with .resolve/.reject/.catch methods */
var getWrappedFetch = function () {
var wrappedPromise = getWrappedPromise();
var args = Array.prototype.slice.call(arguments);// arguments to Array
fetch.apply(null, args)// calling original fetch() method
.then(function (response) {
wrappedPromise.resolve(response);
}, function (error) {
// wrappedPromise.reject(alert("Fetch status: " + error));
wrappedPromise.reject(____mobile.connection_error.top);
console.log("Fetch status: " + error);
})
.catch(function (error) {
wrappedPromise.catch(error);
});
return wrappedPromise;
};
/**
* Fetch JSON by url
* #param { {
* url: {String},
* [cacheBusting]: {Boolean}
* } } params
* #returns {Promise}
*/
var postJSON = function (params) {
var headers1 = {}
if (params.json){
headers1 = {
'Accept': 'application/json',
'Content-Type': 'application/json'}
}
if (params.headersIn){
headers1 = params.headersIn
}
var methodTmp = 'POST'
if (params.methodIn) {
methodTmp = params.methodIn
}
console.log(methodTmp)
var wrappedFetch = getWrappedFetch(
params.cacheBusting ? params.url + '?' + new Date().getTime() : params.url,
{
method: methodTmp,//'POST',// optional, "GET" is default value
headers: headers1,
body: params.send_data
});
var timeoutId = setTimeout(function () {
wrappedFetch.reject(alert(____mobile.connection_error.timeout, ____mobile.connection_error.check_connection));// reject on timeout
}, MAX_WAITING_TIME);
return wrappedFetch.promise// getting clear promise from wrapped
.then(function (response) {
clearTimeout(timeoutId);
return response;
})
.then(processStatus)
.then(parseJson);
};
module.exports = postJSON;
What would be the bast way to implement: NetInfo.isConnected.fetch() so fetched would only worked when there is internet connection?
EDIT:
I want to use:
NetInfo.isConnected.fetch()
Yeah I have to rewrite this code, not to use getWrappedPromise and now I think is good time for it.
EDIT2: Ok I refactored this code fragment, hope its better. Any comments welcome. I tested and I'm not sure if I still need this NetInfo.isConnected.fetch(). Now there is no errors where there is no connection or am I missing something?
New code:
var processStatus = function (response) {
if (response == undefined) {
return null
}
// status "0" to handle local files fetching (e.g. Cordova/Phonegap etc.)
if (response.status === 200 || response.status === 0 || response.status === 201 || response.status === 422 || response.status === 302 ) {
return Promise.resolve(response)
} else if(response.status === 413) {
return Promise.reject(alert(____mobile.connection_error.large_file))
} else {
//return Promise.reject(alert("Process status: "+JSON.stringify(response )))
console.log("Process status: "+JSON.stringify(response ));
return Promise.reject(alert(____mobile.connection_error.top));
}
};
var parseJson = function (response) {
if (response == undefined) {
return null
}
return response.json();
};
var postJSON = function (params) {
var headers1 = {}
if (params.json){
headers1 = {
'Accept': 'application/json',
'Content-Type': 'application/json'}
}
if (params.headersIn){
headers1 = params.headersIn
}
var methodTmp = 'POST'
if (params.methodIn) {
methodTmp = params.methodIn
}
console.log(methodTmp)
var fetchPromise = fetch(params.cacheBusting ? params.url + '?' + new Date().getTime() : params.url,
{
method: methodTmp,//'POST',// optional, "GET" is default value
headers: headers1,
body: params.send_data
})// calling original fetch() method
.then(function (response) {
return response;
}, function (error) {
console.log("Fetch status: " + error);
return fetch
}).then(processStatus)
.then(parseJson);
// timeoutId = setTimeout(function () {
// wrappedFetch.reject(alert(____mobile.connection_error.timeout, ____mobile.connection_error.check_connection));// reject on timeout
// }, MAX_WAITING_TIME);
return fetchPromise
};

Categories