JavaScript: Retry request on fail and continue normal app flow - javascript

Quick context:
The user has a problem with the network and I need to repeat requests to the server to continue loading app instead of showing an error.
Existing code in the app:
ajaxPostWithRootData = function (url, rootData) {
//prepare request
var request = decoratorFunction(
$.ajax({
//standard data required to send request
})
)
.done(function (result) {
// normal flow
})
.fail(function (e) {
//I want to repeat call after 1 second
});
return request;
};
Example call:
return ajaxPostWithRootData(url, data)
.done(function (result) {
//do some stuff
})
.fail(function (e) {
//hide loading, show error message
});
};
I thought to write in fail() something like that:
//repeatOnFail is a new function's param to prevent infinite loop
if (shouldRepeatOnConnectionProblem(repeatOnFail, e)) {
setTimeout(() => ajaxPostWithRootData(url, rootData, false), 1000);
}
Unfortunately JS works asynchronously here and even I get success response in second request, it's too late because app executes code from second fail().
Do you know how to change this code to "back" to the correct flow when I got a success response after the second call?
My knowledge about JS is poor, so even I know have an idea how to fix it, I don't know how to implement a solution ;/

If I understand correctly, you would like to invoke $.ajax({..}) with a fixed configuration and, if that first invocation fails, immediately retry the $.ajax({..}) request with the same configuration, in which case the following changes to ajaxPostWithRootData should achieve what you require:
var ajaxPostWithRootData = function(url, rootData) {
// Define reusable function that sets up and invokes
// ajax request
var doRequest = function() {
return decoratorFunction(
$.ajax({
//standard data required to send request
}))
}
// Return a promise that is controlled by this deferred behavior
return $.Deferred(function(deferred) {
doRequest()
.fail(function (){
doRequest ()
.fail(function () {
// On fail after second attempt, throw error
// to external app flow
deferred.reject();
}).done(function (result){
// If second attempt succeed, continue external
// app flow
deferred.resolve(result);
});
})
.done(function (result){
// If first attempt succeed, continue external
// app flow
deferred.resolve(result);
});
}).promise();
};
There updates should also work nicely with your external code and preserve the overall flow of events that you're after.
Hope that helps!

Related

One function to wait for another function in Javascript (AJAX)

I'm trying to find a way to make one function to hold and only return its output (hold the flow) only when the async function inside it (AJAX call) have completed.
In this click event, the "prepareEstimates" function calls another function, which makes an AJAX call to an API. The ideia is that the code won't move forward until this function has completed everything there, including the AJAX call. Because it's calling the same API, I cannot have two simultaneous call using the same data (issues with the API that are out of scope from this question).
$('#estimateBtn').on("click", function () {
if (validateBasicEstimationForm()) {
prepareEstimates();
if (validateCustomEstimationForm()) {
prepareCustomEstimates();
}
});
function prepareEstimates() {
// check if runAPI should be true or false
if (runApi) {
// make call here
$.when(callAJAXFunction(path, dataForAPICalls)).done(function (result) {
// Do stuff with the result
});
}
};
Then, once this prepareEstimates function has completed, it should move forward and the next one, prepareCustomEstimates, which is very similar to this function, will do the same thing.
function prepareEstimates() {
// check if runAPI should be true or false
if (runApi) {
// make call here
$.when(callAJAXFunction(path, dataForAPICalls)).done(function (result) {
// Do other stuff with the result
});
}
};
and this callAJAXFunction just return an AJAX call
// Invoke API call and get result
return await $.ajax({
url: url,
type: "POST",
dataType: "json",
data: data,
success: function (result) {
},
error: function () {
console.log("error calling Api");
}
});
}
I tried using promise, await, and others, but I couldn't make it work, no matter what I tried.
In the end, what is happening is that the first AJAX call is fired, then right after it, the second ajax call, before the first one has completed.
Any suggestion?
Thank you

More compact way to write ajax queries

I have been writing many web apps with php & mysql & jquery & bootstrap and now it's time address this problem. How to write shorter ajax queries(posting) ?
If I want to write code that works and takes care of many problems, it's too long for every ajax call.
Is there a better way or some library / wrapper that makes the code SHORTER and FASTER to write, but does atleast all these stuff
I looked popular axios, but it seems even worse
//JUST an example code, too complicated
var $btnStatusElem = $("#passwordreset").button('loading');
$.ajax({
type: "POST",
cache: false,
url: "pwreset.php",
data: postdata
success: function(data) {
$btnStatusElem.button('reset');
try {
var datajson = JSON.parse(data);
}
catch (e) {
alert('Unexpected server error');
return false;
};
if (datajson['success'] == true) {
//do the OK stuff
} else {
//show the error code, and stuff
return false;
}
},//success
error: function(msg) {
alert('ERROR');
$('#passwordreset_result').html(msg);
}
});
For my code, ajax query, i want it to do these steps:
1. Disable the submit button while posting (re-enable also after 15 seconds and not just leave it disabled until page refresh)
2. It sends json, expects json to return
3. If server has some error, it DOES NOT return json but error. Then the code will halt all js execution if i dont use try...catch. This is pain to write each time
4. If server returns validation error or some other expected error, i have to detect this and show to the user
5. If all ok, do the stuff
As with any refactoring, identify and isolate the repetitive code and pass in the unique bits. In this case, for example, you could isolate the ajax call and json parsing into a function and pass in the url, data, etc.
That function could return a promise that resolves/rejects as appropriate.
Given the doRequest function below (pseudocode, untested and would probably need a bit of tweaking for real-world use), you could then use it all over the place with fewer keystrokes:
doRequest('pwreset.php', postdata, button)
.then(result => {
// do something with the result
})
.catch(error => {
// deal with the error
});
or
try {
const result = await doRequest('pwreset.php', postdata);
// do something with result
}
catch (e) {
// handle error
}
All of the boilerplate stuff is isolated in doRequest.
async function doRequest(url, data, button, type = "POST") {
return new Promise((fulfill, reject) => {
$.ajax({
type,
url,
data,
cache: false,
success: function(data) {
$btnStatusElem.button('reset');
try {
const datajson = JSON.parse(data);
} catch (e) {
return reject(e);
};
return datajson['success'] == true ?
fulfill(datajson) :
reject(datajson);
}, //success
error: function(msg) {
return reject(msg);
}
});
})
}
As #mister-jojo says, you might also want to consider using the [fetch api] instead of jQuery, but the same principle applies.

AngularJS restangular post callback

I am using restangular with AngularJS, and I need to use some function, which will trigger callback function/promise after POST response.
I tryed setResponseInterceptor which did not helped in this situation.
I need to use this function to lock form from submission until response will come back to client, and then unlock it to future use.
Do you know some magic for this?
I am using this code, and want to fire custom function when POST response will come to the client, something like .then()
reportAPI.createReport = function (apiData) {
return baseReport.customPOST(apiData, 'createreport');
};
var unlockForm = function () {
console.log(new Date().getTime());
};
Restangular.setResponseInterceptor(function (data, operation, what, url, response, deferred) {
if (operation === 'post') {
console.log(new Date().getTime());
console.log(operation);
console.log(what);
console.log(response);
if (response.data) {
unlockForm();
}
return data;
}
});

How do I stop a $interval function to execute if the server has not responded yet?

Let me clarify the question:
I have a function in my controller that uses a service to send an HTTP GET request to see if the status is 'done'. This function runs every 5 secs. However, this would also run in every 5 seconds even if there is no response yet, without killing the previous request.
How do I ensure that if there is no response, in 5 secs, that the previous request is killed? Or is there a better solution to this?
Here's my $interval function:
self.$interval(function setInterval() {
EngineService.getEngines().then(
function getEngines(response) {
self.enginesListFromDatabase = response;
for (const item of self.enginesListFromDatabase) {
if (item.status === 'done' && self.doneArray.indexOf(item.id) === -1) {
self.doneArray.push(item.id);
}
}
}
);
}, self.interval);
The service has this http call:
getEngines() {
return this.$http({
method: 'GET',
url: '/api/engines',
}).then(
function getResponse(res){
return res.data;
}
);
}
I tried looking into the docs, but couldn't make sense of how to use $interval properly for this case, or $q.
You can create a $promise and save it to EngineService for canceling requests.
EngineService.canceler = $q.defer(); // $q is a service u need to inject to EngineService. this is just example that u should assign it to the service
getEngines() {
return this.$http({
method: 'GET',
url: '/api/engines',
timeout:EngineService.canceler
}).then(
function getResponse(res){
return res.data;
}
);
}
and cancel the last request:
self.$interval(function setInterval() {
EngineService.canceler.resolve();
EngineService.getEngines().then(
function getEngines(response) {
self.enginesListFromDatabase = response;
for (const item of self.enginesListFromDatabase) {
if (item.status === 'done' && self.doneArray.indexOf(item.id) === -1) {
self.doneArray.push(item.id);
}
}
}
);
}, self.interval);
Regarding your cancelling the request approach - I think it would be much easier to have a variable in your controller (or service) that holds the done/undone status. That way, in each function in the then(..) section, you can ask:
if (!self.done) {
...
}
And then get on in case that's relevant. Otherwise, all requests' responses would fail at that if and nothing would happen, nothing gets overridden.
To cancel future requests -
You can make use of $interval.cancel method and cancel your interval when you're getting your response.
var intervalFunc = $interval(function () {
...
if (!self.done && gotMyWay) {
$interval.cancel(intervalFunc);
}
}, self.interval);
Fiddle demonstrating the cancel mechanism.

AJAX calling being aborted before it is complete with Backbone.js

I've followed this answer on how to abort all AJAX requests on page redirection. It basically uses ajaxSend to add the xhr request object to an array and then ajaxComplete to remove that object from the array and then if the page is redirected it will loop through that array and abort each request that the browser is still waiting for.
This works fine for all normal AJAX requests using $.ajax({}). However, if a Backbone AJAX request is called like model.save() then the window.onbeforeunload function gets executed before anything happens and causes the model.save() to be aborted before it is event sent to the server.
Anyone have any ideas on how to fix this or why the window.onbeforeunload function is getting called?
Here is the code that I got from the linked answer:
var xhrPool = [];
$.xhrPool = {};
$.xhrPool.abortAll = function () {
$.each(xhrPool, function (idx, jqXHR) {
jqXHR.abort();
});
xhrPool.length = 0;
};
$(document).ajaxSend(function (e, jqXHR, options) {
xhrPool.push(jqXHR);
});
$(document).ajaxComplete(function (e, jqXHR, options) {
xhrPool = $.grep(xhrPool, function (x) {
return x != jqXHR;
});
});
window.onbeforeunload = function () {
$.xhrPool.abortAll();
};
did you tried using the wait flag at the time to save?
like this
model.save(data,{wait:true});
Hope this helps.

Categories

Resources