JavaScript - Wait until multiple ajax requests are done then do something - javascript

I have two ajax calls in JavaScript that add content to the page asynchronously. I need to create a "timeout" function (or something similar) that waits until they're both done loading to then execute more code. Here is what I have so far
https://jsfiddle.net/qcu5asnj/
<div class='resultsSection'>
Example
</div>
<script>
var loading_first = "loading";
var first_ajax = {
url: 'https://example.com/load.php?q=fast',
type: "GET",
success: function( data ) {
console.log("success");
$(".resultsSection").append(data);
loading_first = "done";
}
};
$.ajax( first_ajax );
var loading_second = "loading";
var second_ajax = {
url: 'https://example.com/load.php?q=slow',
type: "GET",
success: function( data ) {
console.log("success");
$(".resultsSection").append(data);
loading_second = "done";
}
};
$.ajax( second_ajax );
// Need to create a function that waits until both are done. I know this is wrong
if((loading_first == "done") && (loading_second == "done")){
console.log("Both done loading, execute more code here");
}
</script>

Store the promises returned by $.ajax in variables and use Promise.all()
const req1 = $.ajax('https://jsonplaceholder.typicode.com/todos/1').then(data => {
console.log('First request done')
return data
})
const req2 = $.ajax('https://jsonplaceholder.typicode.com/todos/2').then(data => {
console.log('Second request done')
return data
})
Promise.all([req1, req2]).then(results=>{
console.log('All done')
console.log('Combined results',results)
}).catch(err=> console.log('Ooops one of the requests failed'))
.as-console-wrapper {max-height: 100%!important;top:0;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Related

Javascript await is only valid in async functions

I have this function to delete items once a popup returns true:
function deleteItems(selectedItems){
if (selectedItems.length > 0) {
$("#confirm-popup-modal").modal("show");
$("#confirm-popup-modal").one('hidden.bs.modal', function (event) {
if ($("#confirm-modal").val() == "true") {
var form_data = selectedItems;
$.ajax({
url: "#Url.Action("Delete", #ViewContext.RouteData.Values["controller"].ToString())",
method: "POST",
data: JSON.stringify(form_data),
contentType: "application/json",
success: function (result) {
if (result.Result == true) {
var deleteId = result.Output;
await CompletedJobsAccess(deleteId);
table.draw();
}
},
error: function (error) {
console.log(error);
}
});
}
});
}
}
Inside the Ajax success is another function called CompletedJobsAccess that will keep looping every 3 seconds to check if a job deletion has been completed:
function CompletedJobsAccess(DeleteId){
return new Promise((resolve,reject)=>{
var loopInterval = setInterval(function() {
$.ajax({
url: "#Url.Action("Verify", "CompletedJobsAccess", new {area="Base" })",
method: "POST",
data: JSON.stringify(DeleteId),
contentType: "application/json",
success: function(verifyResult) {
if (verifyResult.IS_COMPLETED == true && verifyResult.IS_PROCESSING == false) {
if (verifyResult.IS_SUCCESSFUL == true) {
console.log(verifyResult.OUTPUT);
$.each($.parseJSON(verifyResult.OUTPUT), function(index, value) {
if (value.Result == true) {
toastr.success(value.Message);
}else{
toastr.error(value.Message);
}
});
clearInterval(loopInterval);
} else {
toastr.error(verifyResult.ERROR_MESSAGE);
}
}
},
error: function(innerError) {
console.log(innerError);
}
});
}, 3000);
});
}
However, when I load the page, and call deleteItems(selected);, this is the error I get:
Uncaught SyntaxError: await is only valid in async functions and the
top level bodies of modules
I tried searching around but I can't find if it can work within an ajax success function.
EDIT:
Added async to the ajax success function but the table draw function doesn't run.
function deleteItems(selectedItems){
if (selectedItems.length > 0) {
$("#confirm-popup-modal").modal("show");
$("#confirm-popup-modal").one('hidden.bs.modal', function (event) {
if ($("#confirm-modal").val() == "true") {
var form_data = selectedItems;
$.ajax({
url: "#Url.Action("Delete", #ViewContext.RouteData.Values["controller"].ToString())",
method: "POST",
data: JSON.stringify(form_data),
contentType: "application/json",
success: async function (result) {
if (result.Result == true) {
var deleteId = result.Output;
console.log("table before");
await CompletedJobsAccess(deleteId);
console.log("table draw");
table.draw();
}
table.draw();
},
error: function (error) {
console.log(error);
}
});
}
});
}
}
EDIT 2: Updated CompletedJobsAccess to resolve promises:
function CompletedJobsAccess(DeleteId){
return new Promise((resolve,reject)=>{
var loopInterval = setInterval(function() {
$.ajax({
url: "#Url.Action("Verify", "CompletedJobsAccess", new {area="Base" })",
method: "POST",
data: JSON.stringify(DeleteId),
contentType: "application/json",
success: function(verifyResult) {
if (verifyResult.IS_COMPLETED == true && verifyResult.IS_PROCESSING == false) {
if (verifyResult.IS_SUCCESSFUL == true) {
console.log(verifyResult.OUTPUT);
$.each($.parseJSON(verifyResult.OUTPUT), function(index, value) {
if (value.Result == true) {
toastr.success(value.Message);
}else{
toastr.error(value.Message);
}
});
clearInterval(loopInterval);
return Promise.resolve();
} else {
toastr.error(verifyResult.ERROR_MESSAGE);
return Promise.resolve();
}
}
},
error: function(innerError) {
console.log(innerError);
}
});
}, 3000);
});
}
Just make the success function async
$.ajax({
url: "https://jsonplaceholder.typicode.com/users/3",
method: "GET",
success: async function(data) {
console.log("first - now wait a second ...");
await new Promise((res) => setTimeout(res, 1000));
console.log("second, data:",data);
},
error: function(innerError) {
console.log(innerError);
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Working JSFiddle (can't work on this site because of CORS)
In CompletedJobsAccess(DeleteId) you return a promise. But the way you set it up it will never execute the resolve function. So your await will wait forever ...
You could place the line
resolve();
right after
clearInterval(loopInterval);
in your CompletedJobsAccess function to make it work.
Do not return yet another Promise.resolve() like you did in your edited code.
A resolve function for a promise is never returned but executed.
Try Adding async before all the function keyword like async function deleteItems(selectedItems){ and also $("#confirm-popup-modal").one('hidden.bs.modal', async function (event) { and it should do the job.
You're using await in functions that don't use the async keyword. await isn't available in regular functions. To solve this, you can change all the functions using await to async function to make it into an asynchronous function.
And if you don't want want to go through every function to make it asynchronous, you can just put the entire code inside an asynchronous IIFE

How to assign an Ajax response to a variable in another function in React?

I have an ajax call that responds with a list of movies, and then I have an other function with an other ajax call that returns the genre names, since the first call only has the id of the genre, then I relate the genreId with its name and I assign it to the JSON on the first ajax call, kind of like assigning it to a variable. The problem that I have is that since ajax is asynchronous, it always ends up undefined. I don't want to make it synchronous since it will end up in a bad user experience.
First ajax call
$.ajax({
url: urlString,
success: (searchResults) => {
const results = searchResults.results
var movieRows = [];
results.forEach(movie => {
movie.genres = this.getMovieGenres(movie.media_type, movie.genre_ids);
const movieRow = <MovieRow key={movie.id} movie={movie}/>;
movieRows.push(movieRow)
});
this.setState({rows: movieRows})
},
error: (xhr, status, err) => {
console.log("Failed to fetch data...")
}
})
Function that I call with the second ajax call
getMovieGenres (mediaType ,movieGenreIds) {
urlString = `https://api.themoviedb.org/3/genre/movie/list?api_key=${config.movieDBbApiKey}&language=en-US`ApiKey}&language=en-US`
var genres = []
$.ajax({
url: urlString,
async: true,
crossDomain: true,
method: "GET",
success: searchResults => {
for (let i = 0; i < movieGenreIds.length; i++) {
for (let j = 0; j < searchResults.genres.length; i++){
console.log(searchResults.genres[j].id)
if (movieGenreIds[i] === searchResults.genres[j].id) {
genres.push(searchResults.genres[j].name)
}
}
}
},
error: (xhr, status, err) => {
console.log("Failed to fetch data...")
}
})
return genres
}
There are variouse solutions to your problem.
The simplest is to make the second ajax call in the callback of the first ajax call. In the second call you'll have both results from first ajax call and the second. Then make transforms and set state in the second call.
Other options (the reccomended one) is to work with Axios or Fetch that rely on Promises.
Promises can be evaded with Promise.all:
var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, "foo");
});
Promise.all([p1, p2, p3]).then(values => {
console.log(values); // [3, 1337, "foo"]
});
you can try this:
var promise = new Promise((resolve,reject)=> {
return $.ajax({
url: urlString,
success: (searchResults) => {
return resolve(searchResults.results)
},
error: (xhr, status, err) => {
return reject("Failed to fetch data...")
}
})
})
function that return a promise
getMovieGenres (mediaType ,movieGenreIds) {
urlString = `https://api.themoviedb.org/3/genre/movie/list?api_key=${config.movieDBbApiKey}&language=en-US`ApiKey}&language=en-US`
var genres = []
return new Promise((resolve,reject)=> {
$.ajax({
url: urlString,
async: true,
crossDomain: true,
method: "GET",
success: searchResults => {
for (let i = 0; i < movieGenreIds.length; i++) {
for (let j = 0; j < searchResults.genres.length; i++){
console.log(searchResults.genres[j].id)
if (movieGenreIds[i] === searchResults.genres[j].id) {
genres.push(searchResults.genres[j].name)
}
}
}
return resolve(genres)
},
error: (xhr, status, err) => {
return reject("Failed to fetch data...")
}
})
})
}
finally:
promise().then(results => {
var movieRows = [];
var tasks = []
results.forEach(movie => {
tasks.push(getMovieGenres(movie.media_type, movie.genre_ids))
});
Promise.all(tasks).then(output => {
output.forEach(movie => {
const movieRow = <MovieRow key={movie.id} movie={movie}/>;
movieRows.push(movieRow)
})
this.setState({rows: movieRows})
})
})
Hope to help you!

Using jQuery when to defer ajax processing

I have a list of 15+ ajax requests that need to be called in a specific order. I need each ajax call to wait until the previous function finishes before making the next call. This issue arises because my ajax call, has a direct callback that is also an ajax call.
createCheckIn() {
this.selectedList = [...] // long list of objects
count = 0
for ( i=0; i < this.selectedList.length; i++ ) {
$.ajax({
method: "POST",
url: url,
data: {
check_in: {
client_id: this.selectClient.id,
program_id: this.program_id
}
},
success: function(res) {
that.createWeighIn(count, res.id)
count = count + 1
},
error: function(err) {
console.log(err)
}
})
}
},
createWeighIn(index, check_in_id) {
let data = {}
let that = this
data.weigh_in = this.selectedList[index]
$.ajax({
method: "POST",
url: url,
data: data,
success: function(res) {
console.log(res)
},
error: function(err) {
console.log(err)
}
})
}
the correct data is generated but I believe the ordering is off because eventually there is a call to createCheckIn() that begins before the previous entry has completed.
Is there a way to chain these functions such that createCheckIn() and createWeighIn() are called (and complete) before selectedList iterates.
your for loop in createCheckIn() will not stop to wait on your ajax return. you can do something like:
function createCheckIn(oldI, oldCount){
var count = 0;
var currentI = 0;
if(oldCount != null){
count = oldCount;
}
if(oldI != null){
currentI = oldI;
}
if(currentI < this.selectedList.length){
$.ajax({
method: "POST",
url: url,
data: {
check_in: {
client_id: this.selectClient.id,
program_id: this.program_id
}
},
success: function(res) {
that.createWeighIn(count, res.id)
createCheckIn(currentI + 1, count + 1);
},
error: function(err) {
console.log(err)
}
}); //ajax
} // if
}
seems likely that you can eliminate one of those counters too, the i or the count
Seems like this is missing some potentially really important details about what you need to do leading up to this (ie. this.selectedItems generation) and what happens after (what if one call checkin fails, what if a checkin succeeds but its corresponding weighIn fails, etc..). That said...
It seems you are not actually using the counter for anything other than to reference data you already have, so why not just pass that in directly like:
createWeighIn(weighInData, check_in_id) {
let data = {};
let that = this;
data.weigh_in = weighInData;
// ... your other code
}
I would make createCheckIn only handle doing the ajax request and making a single "reservation" in your system. Then i would make a new method called checkIn that uses the two previous method to process all of selected items:
checkIn() {
let self = this;
let promises = [];
let this.selectedList = [...];
for (let = 0; i < this.selectedList.length; i++) {
// always create the deferred outside the call
let def = $.Deferred();
promises.push(def.promise());
this.createCheckIn().done(function (res) {
self.createWeighIn(self.selectedList[i], res.id))
.done(function () {
// resolve
def.resolve.apply(def, Array.prototype.slice.call(arguments);
})
.fail(function () {
def.reject.apply(def, Array.prototype.slice.call(arguments);
});
}).fail(function () {
// if checkin fails, always reject because we know weighIn wont be called
def.reject.apply(def, Array.prototype.slice.call(arguments);
});
};
// this will resolve/fail when all promises (from createWeighIn) resolve/fail
return $.when.apply(null, promises);
}
so putting it all together:
{
createCheckIn() {
let request = $.ajax({
method: "POST",
url: url,
data: {
check_in: {
client_id: this.selectClient.id,
program_id: this.program_id
}
}
})
.fail(function(err) {
console.log(err)
});
};
return request;
},
createWeighIn(data, check_in_id) {
let params = {};
params.weigh_in = data;
let request = $.ajax({
method: "POST",
url: url,
data: params,
success: function(res) {
console.log(res)
},
error: function(err) {
console.log(err)
}
});
return request;
},
checkIn() {
let self = this;
let promises = [];
let this.selectedList = [...];
for (let = 0; i < this.selectedList.length; i++) {
// always create the deferred outside the call
let def = $.Deferred();
promises.push(def.promise());
this.createCheckIn().done(function (res) {
self.createWeighIn(self.selectedList[i], res.id))
.done(function () {
// resolve
def.resolve.apply(def, Array.prototype.slice.call(arguments);
})
.fail(function () {
def.reject.apply(def, Array.prototype.slice.call(arguments);
});
}).fail(function () {
// if checkin fails, always reject because we know weighIn wont be called
def.reject.apply(def, Array.prototype.slice.call(arguments);
});
};
// this will resolve/fail when all promises (from createWeighIn) resolve/fail
return $.when.apply(null, promises);
}
}
I ended up introducing promises, and some recursion and removing the loop altogether. I basically begin the process by calling createCheckIn() with an index of 0:
this.createCheckIn(0)
createCheckIn(index) {
this.selectedList = [...] // long list of objects
count = 0
let prom = new Promise(function(resolve, reject) {
$.ajax({
method: "POST",
url: url,
data: {
check_in: {
client_id: that.selectClient.id,
program_id: that.program_id
}
},
success: function(res) {
resolve(that.createWeighIn(index, res.id))
},
error: function(err) {
reject(console.log(err))
}
})
})
},
createWeighIn(index, check_in_id) {
let data = {}
let that = this
data.weigh_in = this.selectedList[index]
let prom = new Promise(function(resolve, reject) {
$.ajax({
method: "POST",
url: url,
data: data,
success: function(res) {
console.log(res)
if ( index == (that.selectedList.length - 1) ) {
that.complete = true
resolve(console.log("complete"))
} else {
index++
resolve(that.createCheckIn(index))
}
},
error: function(err) {
console.log(err)
}
})
})
}

How can I wait until both ajax requests done?

Here is a simplified of my code:
var res = array();
$.ajax({
url: 'test1.php',
async: true,
success: function (data) {
res[1] = data.result;
}
});
$.ajax({
url: 'test2.php',
async: true,
success: function (data) {
res[2] = data.result;
}
});
if ( /* both ajax request are done */ ) {
// do stuff
} else {
// wait
}
As you can see I've used async: true to run those ajax requests at the same time (in parallel). Now I need to wait until both requests done. How can I determine an ajax request is done? If not wait until it get done?
You can use promises:
Promise.all([
$.ajax({ url: 'test1.php' }),
$.ajax({ url: 'test2.php' })
])
.then(([res1, res2]) => {
// Both requests resolved
})
.catch(error => {
// Something went wrong
});
You can use callback function as well.
var res = [];
function addResults(data) {
res.push(data);
console.log('Request # '+res.length);
if ( res.length >= 2 ) {
// do stuff
console.log('both request has done.');
} else {
// wait
}
}
$.ajax({
url: 'https://jsonplaceholder.typicode.com/posts',
success: function (data) {
addResults(data);
}
});
$.ajax({
url: 'https://jsonplaceholder.typicode.com/posts',
success: function (data) {
addResults(data);
}
});
Use Promise.all function. It will be resolved if all of the promises is resolved and pass the data as array to the then function, else it will be rejected with the first promise failure value
Promise.all([
$.ajax({ url: 'test1.php'}),
$.ajax({ url: 'test2.php'})
])
.then(results => {
// results is an array which contains each promise's resolved value in the call
})
.catch(error => {
// The value of the first rejected promise
});
this official document could help you.
http://api.jquery.com/ajaxStop/
example:
var res = [];
$.ajax({
url: 'test1.php',
async: true,
success: function (data) {
res.push('Ajax one is complete');
}
});
$.ajax({
url: 'test2.php',
async: true,
success: function (data) {
res.push('Ajax two is complete');
}
});
var resALL = function(){
console.log(this)
}
//After the requests all complete
$(document).ajaxStop(resALL.bind(res))

if data exists in localStorage, how to return it, or otherwise perform AJAX request?

Sorry for poor wording. Here's the code
getJSON : function(url) {
if ( localStorage.getItem(url) ) {
console.log('Getting from localStorage instead');
return $.parseJSON(localStorage.getItem(url));
} else {
console.log('No results locally, making XHR request...');
return $.ajax({
url: 'curl.php',
type: 'GET',
data: {
url : url
}
}).done(function (result) {
var json = $.parseJSON(result);
console.log('Setting to localStorage with key:', url);
localStorage.setItem(url, result);
})
}
}
Two ways of getting the data:
If it's in localStorage, this works:
console.log(this.getJSON(url))
If it's not and it's returning the AJAX object:
this.getJSON(url).done(function (result) {
console.log(result);
})
How can this be reformatted so that I can get the data without anticipating of if it's in localStorage or not?
You could add a callback parameter to your getJSON method like so:
this.getJSON = function(callback)
{
if ( localStorage.getItem(url) ) {
console.log('Getting from localStorage instead');
// If it's in local storage execute callback immediately
callback($.parseJSON(localStorage.getItem(url)));
} else {
console.log('No results locally, making XHR request...');
$.ajax({
url: 'curl.php',
type: 'GET',
data: {
url : url
}
}).done(function (result) {
var json = $.parseJSON(result);
console.log('Setting to localStorage with key:', url);
localStorage.setItem(url, result);
callback(json);
});
}
}
You could then call
getJSON(function(json) {
// Do something with json
});

Categories

Resources