Wait for async calls inside a loop to finish - javascript

I know questions similar to this have been asked many times, but some are old and suggest deprecated solutions, some describe a solution but not in the context of a loop, and none of them directly answers my question. I want to know what is the latest and greatest approach to having a piece of code run after another piece of code is done running a bunch of async calls.
Here is the general structure of the code as I have it:
function fetchProperty(input) {
$.ajax({
url: 'https://jsonplaceholder.typicode.com/todos/1' + input
}).done(function(resp){
return $.parseJSON(resp).title;
});
}
inputValues = [1, 2];
outputValues = [];
$.each(inputValues, function(index, value){
outputValues.push(fetchProperty(value));
});
console.log(outputValues); // will return an empty array
Essentially, I want that last line of code to not be executed until after all of the AJAX calls made inside the $.each() are finished. Obviously, I don't want to use async: false because it is deprecated. What is the best practice on how to defer the console.log until after all of the other deferred AJAX calls are done?

Try to use async await, like in the code below.
The problem seems to be that the fetch is async but the console log is not so it print before it fetch the data
async function fetchProperty(input) {
const resp = await $.ajax({
url: 'http://some.api/' + input + '/foobar'
});
return $.parseJSON(resp).someProperty;
}
inputValues = ['this', 'that'];
outputValues = [];
$.each(inputValues, async function(index, value){
outputValues.push(await fetchProperty(value));
});
console.log(outputValues);

I'm not sure that $.ajax return a thenable (Promise like) object or not. Then I will convert $.ajax to the Promise first, and use await keyword to get the value from the function. I use for...of instead of $.each to this task, because $.each use callback style, then it will hard to make it working with async/await.
function fetchProperty(input) {
return new Promise((resolve) => { // return a Promise
$.ajax({
url: 'https://jsonplaceholder.typicode.com/todos/' + input
}).done(function (resp) {
resolve(resp.title); // resolve here
});
});
}
async function main() {
const inputValues = [1, 2];
const outputValues = [];
for (const value of inputValues) {
outputValues.push(await fetchProperty(value));
}
console.log(outputValues); // the values
}
main();

Related

How can wait for ajax call end before relooping in foreach?

I am trying to use Promises but I don't know if in this case, are useful.
$.getJSON("url", "data", function(json) {
json.forEach(function(item) {
//do something
$.get("url", "data", function(response) {
//MAKE SURE THIS BODY IS SOLVED BEFORE RELOOP???
});
})
})
The only way I found is to use ajax with async as false.
Is there a way to solve this?
Maybe using Promises for example? I tried to think a way to use Promises in a foreach loop but didn't find any solution.
I tried this one:
$.getJSON("url", "data", function(json) {
json.forEach(function(item) {
//do something
when($.get("url", "data") {
//do something
})).done(function(response){
//do something
});
})
})
But it doesn't work. foreach always relooped.
As an alternative, you could try re-constructing your data from outer-call to build an array and make use of a function to reflect the looping mechanism and initiate each iteration when the previous inner-call finishes (successfully or not).
Check this out. Might this be a viable solution for you?
$.getJSON("url", "data", function(json)
{
var jarray=[];
var i=0;
for (var xitem in jsonObj) {
//console.log(jsonObj[xitem]);
jarray[i++]=jsonObj[xitem];
}
nextIteration(jarray,0);
})
function nextIteration(sourceArray, nextIterationIndex)
{
if(nextIterationIndex<sourceArray.length)
{
var item = sourceArray[nextIterationIndex];
//do something
$.get("url", "data", function(response)
{
//MAKE SURE THIS BODY IS SOLVED BEFORE RELOOP???
nextIteration(sourceArray,nextIterationIndex+1);
});
}
}
This may be of interest to you... As you wish to perform many ajax request one by one in a loop.
Using await in an async function, you can wait for an ajax resquest to complete before doing the next one.
Below, I used $.getJSON in a for loop... And the console logs clearly show that each request where made one by one.
The $.ajax(), $.post(), $.get(), $.getJSON(), etc. all are returning a promise. So await can be used. Instead of using the callback function, you have to assign the response to a variable.
as in:
let response = await $.getJSON(url);
instead of:
$.getJSON(url, function(response){...});
let url = "https://jsonplaceholder.typicode.com/todos/";
let max = 40;
async function loop() {
for (i = 1; i <= max; i++) {
console.log(`=============================== Loop ${i}`);
let response = await $.getJSON(url + i);
console.log(response);
$("#result").append($("<div>").text(response.title));
console.log(`request ${i} is finished`);
}
}
loop();
-- That is working in CodePen since the SO snippets are squarely blocking any ajax request.

Running two functions in parallel and then calling another function with result of prev functions in javascript

My javascript code..
function testdata(){
var strtest_data='';
var strtest1_data='';
d3.json("js/test.json").then(function(test_json) {
strtest_data=JSON.stringify(test_json);
console.log(strtest_data);
})
d3.json("js/test.json").then(function(test_json1) {
strtest1_data=JSON.stringify(test_json1);
console.log(strtest_data1);
})
console.log(strtest_data);
var testobj=JSON.parse(strtest_data);
var testobj1=JSON.parse(strtest_data1);
console.log(JSON.stringify({data:testobj.quiz, more:testobj1.maths}));
}
In the above code I'm calling two functions using d3.json (for brevity in this question I'm calling for the same test.json) and then with the results calling another function. Here the function is console.log but in actual scenario could be a ajax POST call using fetch.
The above code does not work as the bottom part of the code gets run prior to d3.json getting finished..
I would sincerely appreciate any help in solving this.
Thanks
d3.json is asynchronous method, so if you want to call it sequentially, use async/await
async function testdata() {
let strtest_data = ""
let strtest1_data = ""
const test_json = await d3.json("js/test.json")
strtest_data = JSON.stringify(test_json)
console.log(strtest_data)
const test_json1 = await d3.json("js/test.json")
strtest1_data = JSON.stringify(test_json1)
console.log(strtest_data1)
var testobj = JSON.parse(strtest_data)
var testobj1 = JSON.parse(strtest_data1)
console.log(JSON.stringify({ data: testobj.quiz, more: testobj1.maths }))
}
or call it concurently using Promise.all
async function testdata() {
let strtest_data = ""
let strtest1_data = ""
const [test_json, test_json1] = await Promise.all([
d3.json("js/test.json"),
d3.json("js/test.json"),
])
strtest_data = JSON.stringify(test_json)
strtest1_data = JSON.stringify(test_json1)
console.log(strtest_data)
console.log(strtest_data1)
var testobj = JSON.parse(strtest_data)
var testobj1 = JSON.parse(strtest_data1)
console.log(JSON.stringify({ data: testobj.quiz, more: testobj1.maths }))
}
Here I think
Promise.all([ d3.json("js/test.json")
d3.json("js/test.json")].then(data =>{
//call your other function and access using data array and make your call
}).catch(e=>console.error("error ",e));
This function is going to execute both calls when they finish then it will put the result in data array. If any error comes in either of the call you will have it in the catch block. You can do whatever you want to in the then block if all calls are completed successfully.

jQuery Promise async await in an object

I have a object with loads of functions inside. some of them are ajax calls. the idea is to call one of this functions from another place and wait until it's resolved but i don't know if it is possible and if it is im not sure how to do it. here is the code so you get an idea of what i'm trying to do.
$(document).ready(function(){
$(".button").click(function(){
let some_id = $(this).val();
let ajax_test = binder.firstLayer['ajaxcall'](some_id);
console.log(ajax_test);
});
});
Then i have an object with all the functions i use, among them this ajax call looking something like this:
let binder = {
firstLayer : {
ajaxcall: function(some_id){
return new Promise( (resolve, reject) => {
$.ajax({
// all the details
}).then((response) => {
resolve(response);
});
});
}
}
}
I haven't included the keywords async and await because im not sure where to put them. in the case of the ajaxcall function, i tried putting the async after the ajaxcall: and before function(some_id) but when i put await like:
let ajax_test = await binder.firstLayer['ajaxcall'](some_id);
but it doesn't work. so again, the question is if it can be done the way im trying to do it and if so, what im doing wrong or putting in the wrong place. with this attempt i get undefined if i try to look into the response or [object Promise] (from console.log(ajax_test);) if i just console log the response (from same place as before)
you can use let ajax_test = await binder.firstLayer['ajaxcall'](some_id); as long as you mark the function as async:
$(".button").click(async function(){ ...

Return an Array from an Async call, then additional Async calls for each element of the array

I'm writing an application in javascript where I make a CORS request to the server to grab a data array.
Then, for each item in the array, I need to make another CORS call to get additional info on that element.
I originally thought I could return values from my CORS request like:
data = getData(param);
But apparently you can't mix synchronous and asynchronous code.
What's the best way to accomplish this?
Promises. Here's how you might use them using your requirements, and a setTimeout to mimic an AJAX request.
getData returns a new promise. In this case if the function is called with no params an array is sent back after a second (your first request). If a param is passed into the function 100 is added to the param before resolving - the later requests.
function getData(param) {
return new Promise(function(resolve, reject) {
if (param) {
setTimeout(() => resolve(param + 100), 500);
} else {
setTimeout(() => resolve([1, 2, 3, 4, 5]), 1000)
}
});
}
Call getData without a param and [1, 2, 3, 4, 5] is returned. then we map over the array elements and return new promises for each of them. then we use Promise.all to resolve those promises and then we output the final array [101, 102, 103, 104, 105].
getData()
.then((arr) => arr.map(el => getData(el)))
.then(arr => Promise.all(arr))
.then(arr => console.log(arr));
DEMO
So you can see that you can run one AJAX request and then run more based on the result of the value that's returned until all requests have been made.
You can use async.series. checkout https://github.com/caolan/async . Very good library to solve problem like this - process an array data asynchronously(My favourite).
Or
You can use js promise from https://www.promisejs.org/
Or play with callbacks... like below
Note: Below functions are indicative functions just to show how you can approach the problem as you haven't shared any code. Change them accordingly. Also there might be syntactical/spell error as the code is written directly here.
function ajaxRequester(method,uri, data, onSuccess, onError){ // you can make this function as per requirement.
$.ajax({
type: method,
url: uri,
data: data
success: function(response){
onSuccess(response);
}
});
}
function yourFunction(){
ajaxRequester('GET',urlOf1stRequest,dataToSend,function(resp){
// assuming resp is the array received from server. we'll start with 0th element
processArray(0,resp, function(){
// do your final stuff
});
});
}
function processArray(index, arr, onComplete){
if(index < arr.lenght){
var objToProcess = arr[index]; // get your data to process
ajaxRequester(yourMethod,obj.url, obj.data, function(resp){
// do work with your response variable resp
processArray(++index, arr); // process next element of array after completion of current
});
} else {
onComplete(); // all elements are processed call final callback
}
}

Structuring promises within angularjs

I have done a lot of reading around this, but ultimately the tutorials and guides I have found differ too much for me to get a decent grasp on this concept.
This is what I want to achieve:
1) Simple http request from our server [Any API for demonstration]
2) Run a function with data from (1). [Remove a property from the object]
3) Use result and length of (2) to run a loop of $http requests to our server. [Or any server]
4) This will result in 6 different objects. Run a function on these 6 objects. [Add a property]
5) Once ALL of this is done, run a separate function [Log "finished"]
How can this be achieved using promises? How do I pass data from (1) via a promise to (2)? Is this the right way to achieve what I need to do?
If anyone can show me how this should be structured it would be immensely helpful; I have kept the functions as simple as possible for this question.
Yes, promises are very nice to structure solutions for this kind of problems.
Simplified solution (more or less pseudo-code):
$http(...)
.then(function(response) {
// do something with response, for example:
var list = reponse.data.list;
// return it so that you can use it in the next 'then'.
return list;
})
.then(function(list) {
var promises = [];
angular.forEach(list, function(item) {
// perform a request for each item
var promise = $http(...).then(function(itemResponse) {
itemResponse.extraProperty = true;
return itemResponse;
});
// we make an array of promises
promises.push(promise);
});
// combine all promises into one and return it for the next then()
return $q.all(promises);
})
.then(function(itemsList) {
// itemsList is now an array of all parsed item responses.
console.log(itemsList);
});
(Hopefully this is right, I did not tested it.)
As you can see, you can return values in a callback to pass it to the next then(), or you can pass a promise, and this will result in calling the next callback when it resolves. $q.all() is used to combine multiple promises into one and resolve if all are resolved.
Edit: I realised that you can optionally leave out these three lines:
return list;
})
.then(function(list) {
But it is nice syntax though, because the separation of tasks is more visible.
Check code below, it could contains syntax error, the important is the structure. Step3 contains multiple(6) $http requests, it waits until the last request response to return a unique response object (array) containing response for each $http requets.
//Step 1
var Step1 = function () {
$http.get('api/controller').success(function (resp) {
var object1 = resp;
Step2(object1);
Step3(object1).then(function (resp) {
//resp.data is an array containing the response of each $http request
Step4(resp);
Step5();
});
});
}
//Step2
var Step2 = function(obj){
//do whatever with the object
}
//Step3
var Step3 = function (object1) {
var call = $q.defer();
var get1 = $http.get(object1[0].url);
var get2 = $http.get(object[1].url2);
//...
var get6 = $http.get(object[5].url6);
$q.all([get1, get2,..get6]).then(function (resp) {
call.resolve(resp);
});
return call.promise;
}
//Step4
var Step4 = function (resp) {
for (var i=0; i<resp.data.lenght;i++){
DoWhatEver(resp.data[i]);
};
}
//Step5
var Step5 = function () {
alert("Finished");
}
Step1(); //Call Step1 function
Don't know why you have difficulty implementing this, but maybe $q.all() is what you're missing:
var config1={method:'GET',url:'/api/...'};
$http(config1).success(function(resultsFrom1){
functionForResultsOf1(resultsFrom1);
})
var functionForResultsOf1 = function(resultsOf1){
//remove something from the result, assuming this is a synchronous operation
resultsOf1.splice()...;
var promises=makePromises(*pass whatever you want*);
$q.all(promises).then(function(aggregateOfAllCallsToServer){
angular.forEach(aggregateOfAllCallsToServer,function(data){
//do something to data from each call to the server
})
console.log("finished");
})
}
var makePromises = function(serverUrls){
var promises = [];
angular.forEach(serverUrls, function(url) {
var promise=$http({
url : '/api/'+url,
method: 'GET',
})
promises.push(promise);
});
return $q.all(promises);
}

Categories

Resources