How to run Asynchronous Javascript Functions in order - javascript

Hi I am a beginner programmer and I need to run several javascript functions in an order on a page; getcampaignID(), search1(), searchresult(), search2(), searchresult(). I need to retrieve the campaign ID first to send it over to search1(), get the result, then running search2() to get its result next.
I have successfully ran [search1() + searchresult()] before [search2() + searchresult()] by executing the search1() after the </body> tag and adding a setTimeout in searchresult(). However, I am unable to run getcampaignID first without breaking search1() and search2()
My code looks like this: home.html
<script>
getcampaignID() {
//...make AJAX call to get campaignID
campaignID = xmlhttp.responseText.trim();
}
getcampaignID(); //run function
searchresult() {
//...retrieves search results
//...appends to html div
setTimeout(function () {
if (counter == 0) {
counter = 1;
search2();
} else {
clearTimeout(timer);
}
}, 500);
} //end of searchresults
search1() {
//...search1 parameters
//url=?camid = campaignID
//campaignID unidentified
}
search2() {
//...search2 parameters
//url=?camid = campaignID
//campaignID unidentified
}
</script>
<body>
<div id= results1>...</div>
<div id= results2>...</div>
</body>
<script>
search1();
</script>
Things I have tried:
getcampaignID() {
//... all the codes mentioned
search1() {
alert("search1 working");
}
search2() {
alert("search2 working");
}
}
search1();
Problem: search1() won't run, no alerts fired.
getcampaignID() {
var campaignid = "A";
big(campaignid);
}
big
function (campaignid) {
//..all codes
search1() {
alert("search1 working");
}
search2() {
alert("search2 working");
}
search1();
}
search1();
Problem: search1() won't run, no alerts fired.
Summary:
I am looking for a way to add campaignID value in search1(); before search1 runs

What you need is ES6 Promises. Using Promises you could chain them using .then() method and run them one after another and handle results in chain.
Using promises you could write something like
Promise.resolve().then(
search1();
).then(
doSomethingWithResults(results);
).then(
// ....
);
You could find a good introduction to Promises here: https://davidwalsh.name/promises

You can achieve what you are asking with Promises. They can take a little bit of getting used to, but once you get the hand of it, it makes the asynchronous control flow you are having issues with really simple.
If you used a library to perform your AJAX requests, that returned a Promise itself like Fetch, you could write your code like this:
//Fetch will return a promise the resolves with a value of your campaign ID
fetch('http://endpoint.com/campaign')
.then(campaignId => {
//You can pass that ID to each of your searches.
//Promise.all will resolve with an array containing results from each function you passed in, in that order.
return Promise.all([search1(campaignId), search2(campaignId)]);
})
.then(results => {
//Search 1 was the first promise we passed to Promise.all
let search1Results = results[0];
//Search 2 was the second promise we passed to Promise.all
let search2Results = results[1];
//process the results
});

So there are some possibilities:
Nest the functions. Run search2() inside the callback of search1().
Use jQuery and .defer();
Learn about promises and do same as 2. but without jQuery :)

Related

AngularJS $q.all - wait between http calls

So I have a situation where I need to perform a bunch of http calls, then once they are complete, continue on to the next step in the process.
Below is the code which does this and works fine.
However, I now need to wait a few seconds between each of the http calls. Is there a way to pass in a timeout with my current set up, or will it involve a good bit of refactoring?
Can post more code if needs be. I have tried passing in a timeout config varable into the http call, however, they still get fired at the same time.
Any advice would be great.
Code
var allThings = array.map(function(object) {
var singleThingPromise = getFile(object.id);
return singleThingPromise;
});
$q.all(allThings).then(function() {
deferred.resolve('Finished');
}, function(error) {
deferred.reject(error);
});
Instead of using $q.all, you might want to perform sequential calls one on success of previous and probably with use of $timeout. Maybe you could build a recursive function.
Something like this..
function performSequentialCalls (index) {
if(angular.isUndefined(array[index])) {
return;
}
getFile(array[index].id).then(function() {
$timeout(function() {
performSequentialCalls(index + 1)
}, 1000) // waiting 1 sec after each call
})
}
Inject required stuff properly. This assumes array to contain objects with ids using which you perform API calls. Also assumes that you are using $http. If using $resource, add $promise accordingly.
Hope that helps a bit!
function getItemsWithDelay(index) {
getFile(object[index].id).then(()=>{
setTimeout(()=>{
if(index+1 > object.length) { return }
getItemsWithDelay(index+1)
}, 5000)
})
}
You can make sequential calls
This is a awesome trick question to be asked in an interview, anyways I had a similar requirement and did some research on the internet and thanks to reference https://codehandbook.org/understanding-settimeout-inside-for-loop-in-javascript
I was able to delay all promise call in angularjs and the same can be applied in normal JS syntax as well.
I need to send tasks to a TTP API, and they requested to add a delay in each call
_sendTasks: function(taskMeta) {
var defer = $q.defer();
var promiseArray = [];
const delayIncrement = 1000 * 5;
let delay = 0;
for (i = 0; i < taskMeta.length; i++) {
// using 'let' keyword is VERY IMPORTANT else 'var' will send the same task in all http calls
let requestTask = {
"action": "SOME_ACTION",
"userId": '',
"sessionId": '',
};
// new Promise can be replaced with $q - you can try that, I haven't test it although.
promiseArray.push(new Promise(() => setTimeout(() => $http.post(config.API_ROOT_URL + '/' + requestTask.action, requestTask), delay)));
delay += delayIncrement;
}
$q.all(promiseArray).
then(function(results) {
// handle the results and resolve it at the end
defer.resolve(allResponses);
})
.catch(error => {
console.log(error);
defer.reject("failed to execute");
});
return defer.promise;
}
Note:: using 'let' keyword in FOR loop is VERY IMPORTANT else 'var' will send the same task in all http calls - due to closure/context getting switched

JavaScript - slow down a loop

I have these codes in controller that call webservice using $http get to retrieve some data. Here is the following code:
UPDATE CODES:
var boolGetBoothCoords = false;
BoothDesignatedCoordsService.getBoothDesignatedCoords(strListShortListedBooth[i], 3)
.then(function(response) {
var data = response.data
console.log('data', data.arrBoothDesignatedCoords)
boothDesignatedCoords = data.arrBoothDesignatedCoords;
boolGetBoothCoords = true;
})
console.log("boothDesignatedCoords ", boothDesignatedCoords ); // undefined
// And a lot of other codes
However, since $http get is asynchronous method, the program will invoke the console log and the codes after immediately and boothDesignatedCoords will be undefined. I do not want that. I want the program to invoke the console log and the codes after ONLY when the webservice consumption is completed. So I did the following using this answer: how to slow down a javascript loop:
go();
function go() {
console.log("hello");
if (boolGetBoothCoords == false) {
setTimeout(go, 1000);
}
else
{
}
}
go()
console.log("boothDesignatedCoords ", boothDesignatedCoords ); // undefined
// OTHER CODES that uses variable boothDesignatedCoords will be undefined as well
However, I do not know why it will still invoke the console log but the web service consumption is not completed yet, despite using this method. Can someone please help me? Thanks.
setTimeout is asynchronous, so actually you don't really make a difference calling go function.
What will happen is:
call go() function
call setTimeout inside the function - that will schedule go to be called in (roughly) 1s
call console.log immediately after that
What you probably want is to put your console.log in then callback like that:
var boolGetBoothCoords = false;
BoothDesignatedCoordsService.getBoothDesignatedCoords(strListShortListedBooth[i], 3)
.then(function(response) {
return response.data.arrBoothDesignatedCoords;
})
.then(function(boothDesignatedCoords) {
console.log("boothDesignatedCoords ", boothDesignatedCoords );
});
The other option (not recommended) would be to put console.log in the else part of the if statement in your go function.
In this case, you should also define boothDesignatedCoords before the whole code snippet.
The code that suppose to be run after the response, should be invoked after the response.
var boolGetBoothCoords = false;
BoothDesignatedCoordsService.getBoothDesignatedCoords(
strListShortListedBooth[i], 3)
.then(function(response) {
var data = response.data
console.log('data', data.arrBoothDesignatedCoords)
boothDesignatedCoords = data.arrBoothDesignatedCoords;
boolGetBoothCoords = true;
codeThatUsesTheResponseData();
});
function codeThatUsesTheResponseData() {
console.log("boothDesignatedCoords ", boothDesignatedCoords ); // undefined
// And a lot of other codes
}

Execute dynamic number of ajax request sequentially

Have the following scenario :
I have to display the graphs for a given interval (startDate,endDate)
Because the interval might be quite big , the data is retrieved per day so I need
to do multiple ajax calls sequentially and to append the data to the graph(highcharts)
Example interval is n days ==>
ajax request day 1
when is (done) ready ajax request day 2
when is (done) ready ajax request day 3
....
ajax request day n
I read about deferred and promises BUT I found difficult to with dynamic number of days and the requirement to get the responses sequentially
Thanks
If you're able to store the list of dates in an array, you can use something like this:
var items = ['Apple', 'Orange', 'Banana', 'Alphalpha'];
//replaceable with any function that returns a promise
function asyncFunction(item) {
return $.ajax({
url: '/echo/html',
type: 'POST',
data : item
})
.then(function(data){
$('body').append('<div>Got the response from '+item+'</div>');
//stuff stuff stuff
});
}
function sequence(arr, callback) {
var i=0;
var request = function(item) {
return callback(item).then(function(){
if (i < arr.length-1)
return request(arr[++i]);
});
}
return request(arr[i]);
}
sequence(items, asyncFunction).then(function(){
$('body').append('<div>Done with all!</div>');
});
https://jsfiddle.net/7ojy9jnx/2/
Basically, sequence takes an Array of items and runs a function on all of them (in this case asyncFunctions, which can be replaced with any function), a function that returns a promise.
This is very basic implementation, you'll notice, for example, it has no error handling. Libraries like async.js have an exhaustive list of tools that accomplish tasks like this, but who knows, maybe this will suffice.
Not sure if you already figured it out, but a good way to tackle your problem would be using a combination of jQuery.Deferred and recursion. Check out this sample code and see if it helps clarify things:
function getData(dayLimit) {
var allDone = $.Deferred();
var getDataForDay = function(day) {
doAsyncThing(day).done(function() {
if (day < dayLimit) {
getDataForDay(day + 1);
} else {
allDone.resolve();
}
}).fail(function(){
/*
Reject the deferred if one of your operations fails.
Useful if you're binding "fail" or "always" callbacks
to the promise returned by getData.
*/
allDone.reject();
});
};
getDataForDay(1); //start with first day
return allDone.promise();
}
Let me know if you need more clarification, happy to help!
What about recursively calling. Create a parameterized function and pass the day to the function like,
function getDetails(day) {
// ajax call
// In the callbacks call the getDetails function by updating the date
}
If you are using Jquery in your Application try pushing all the ajax to an array
ex
[ajax,ajax,...]
and then user
$.when([ajax,ajax,...]).then(function(){
console.log(arguments);// you will get the success messages in arguments array
})

Global array not updating after .push inside function [duplicate]

This question already has answers here:
Returning value from asynchronous JavaScript method?
(2 answers)
Closed 7 years ago.
Hey guys anyone who can help my out?
Basically I want to make a breaking news footer that loops through the newsWire array and updates the text automatically. Problem is when I run my console.log(newsWire.length) outside the loadNewswire function it returns a 0, while the console.log inside returns 40 as it should?
Link: http://jsfiddle.net/u8y8zh72/3/
<html>
<head>
<script src="https://code.jquery.com/jquery-2.1.4.js"></script>
<style>
footer {
height: 75px;
position: fixed;
bottom: 0;
}
</style>
</head>
<body>
<div class="container">
<ul id="js-news" class="js-hidden"></ul>
</div>
<footer>
<div class="container" id="newswiretxt">
<span></span>
</div>
</footer>
</body>
<script type="text/javascript">
var newsWire = [];
function loadNewswire() {
$.getJSON('http://api.nytimes.com/svc/news/v3/content/all/all.json',
{'api-key': 'XXXXXXXXX'},
function(data) {
console.log(data)
var newsWireTemp = [];
for (var i = 0; i < data.results.length; i++) {
var breakingNews = data.results[i];
var breakingTitle = breakingNews.title;
var breakingAbstract = breakingNews.abstract;
newsWireTemp.push(breakingTitle);
newsWireTemp.push(breakingAbstract);
}
newsWire = newsWireTemp;
console.log(newsWire.length);
});
}
loadNewswire();
console.log(newsWire.length);
$(document).ready(function() {
var items = newsWire;
$text = $('#newswiretxt span'),
delay = 10; //seconds
function loop (delay) {
$.each(items, function (i, elm){
$text.delay(delay*1E3).fadeOut();
$text.queue(function(){
$text.html(items[i]);
$text.dequeue();
});
$text.fadeIn();
$text.queue(function(){
if (i == items.length -1) {
loop(delay);
}
$text.dequeue();
});
});
}
loop(delay);
});
</script>
The main thing is this:
...
loadNewswire();
console.log(newsWire.length);
...
When you call loadNewsWire, you're starting an asynchronous JSON request. However, the script execution won't wait for that function to complete, so it immediately runs the following console.log statement. At that point, the JSON request hasn't completed, so the newsWire array is still empty - which is why console.log(newsWire.length) returns 0 there.
Inside your loadNewsWire function, you have a callback function that gets executed when the JSON request has returned your data. At this point you're populating the array, and console.log(newsWire.length) gives you the expected count.
Update in response to comment:
Is there anyway to make the rest of my code wait for the function to
execute?
Yes! $.getJSON is a convenience wrapper for $.ajax, which returns a jqXHR object (full juicy details in the jQuery documentation). You can add additional callbacks to that object, which is what you're actually doing inline in your call to $.getJSON. The following:
$.getJSON('http://...', { 'api-key': '...' },
function (data) {
// stuff
});
Is equivalent to:
$.getJSON('http://...', { 'api-key': '...' })
.done(function (data) {
// stuff
});
So, if you modify your loadNewswire to return the object returned from $.getJSON, you can attach a callback to it that will wait for the async operation to complete, and place the rest of your code inside that. If you change your code to this:
function loadNewswire() {
return $.getJSON(
...
);
};
You can then wrap the code you want to wait using one of the done, fail or always callbacks.
Your calling code would then look like this:
loadNewswire().done(function () {
console.log(newsWire.length);
// and any other code you want to hold until the async operation is complete
});
I'd suggest reading through the previously mentioned documentation - it's a bit heavy, but it gives a good overview of how to work with async requests using jQuery.

Async request into for loop angular.js

I have an array and i need to send values of array to webservice through http post request one by one . For the node.js , i'm using "async" package to do that for ex: async.eachSeries doing it well , how can i do that same thing for angular.js , my normal async code;
//this code sends all queries of array (maybe 5.000 request at same time , it is hard to process for webservice :=) ) at same time and wait for all responses.
//it works but actually for me , responses should wait others at end of loop should work one by one
//like async.eachSeries module!
for (var i = 0; i < myArr.lenght; i++) {
(function (i) {
var data = {
"myQuery": myArr[i].query
};
$http.post("/myServiceUrl", data).success(function (result) {
console.log(result);
});
})(i);
}
Both Matt Way and Chris L answers Correct , you can investigate Chris's answer for understanding about async to sync functions in for loops.
You can use $q to create a similar requirement by chaining promises together. For example:
var chain = $q.when();
angular.forEach(myArr, function(item){
chain = chain.then(function(){
var data = {
myQuery: item.query
};
return $http.post('/myServiceUrl', data).success(function(result){
console.log(result);
});
});
});
// the final chain object will resolve once all the posts have completed.
chain.then(function(){
console.log('all done!');
});
Essentially you are just running the next promise once the previous one has completed. Emphasis here on the fact that each request will wait until the previous one has completed, as per your question.
function logResultFromWebService(value)
{
$http.post("/myServiceUrl", value).success(console.log);
}
angular.forEach(myArray, logResultFromWebService);
If I understand your question correctly. You want to run a for loop in a synchronized manner such that the next iteration only occurs once the previous iteration is completed. For that, you can use a synchronized loop/callbacks. Especially if the order matters.
var syncLoop = function (iterations, process, exit) {
var index = 0,
done = false,
shouldExit = false;
var loop = {
next: function () {
if (done) {
if (shouldExit && exit) {
return exit(); // Exit if we're done
}
}
// If we're not finished
if (index < iterations) {
index++; // Increment our index
process(loop); // Run our process, pass in the loop
// Otherwise we're done
} else {
done = true; // Make sure we say we're done
if (exit) exit(); // Call the callback on exit
}
},
iteration: function () {
return index - 1; // Return the loop number we're on
},
break: function (end) {
done = true; // End the loop
shouldExit = end; // Passing end as true means we still call the exit callback
}
};
console.log('running first time');
loop.next();
return loop;
}
For your particular implementation:
syncLoop(myArray.length, function (loop) {
var index = loop.iteration();
var data = {
"myQuery": myArray[index].query
};
$http.post("/myServiceUrl", data).success(function (result) {
console.log(result);
loop.next();
});
}, function () {
console.log('done');
});
If you intend on doing something with the data once returned (such as perform calculations) you can do so with this method because you will return the data in a specified order.
I implemented something similar in a statistical calculation web app I built.
EDIT:
To illustrate the problem I had when using $q.when I have set up a fiddle. Hopefully this will help illustrate why I did this the way I did.
https://jsfiddle.net/chrislewispac/6atp3w8o/
Using the following code from Matt's answer:
var chain = $q.when(promise.getResult());
angular.forEach(myArr, function (item) {
chain = chain.then(function () {
$rootScope.status = item;
console.log(item);
});
});
// the final chain object will resolve once all the posts have completed.
chain.then(function () {
console.log('all done!');
});
And this fiddle is an example of my solution:
https://jsfiddle.net/chrislewispac/Lgwteone/3/
Compare the $q version to my version. View the console and imagine those being delivered to the user interface for user intervention in the process and/or performing statistical operations on the sequential returns.
You will see that it does not sequentially give the numbers 1,2,3,4 etc. either in the console or in the view in Matt's answer. It 'batches' the responses and then returns them. Therefore, if step 3 is not to be run depending on the response in step 2 there is not, at least in the answer provided, a way to break out or explicitly control the synchronous operation here. This presents a significant problem when attempting to perform sequential calculations and/or allow the user to control break points, etc.
Now, I am digging through both the $q libraries and the Q library to see if there is a more elegant solution for this problem. However, my solution does work as requested and is very explicit which allows me to place the function in a service and manipulate for certain use cases at my will because I completely understand what it is doing. For me, that is more important than using a library (at least at this stage in my development as a programmer and I am sure there are lots of other people at the same stage on StackOverflow as well).
If the order doesn't matter in which they are sent
var items = [/* your array */];
var promises = [];
angular.forEach(items, function(value, key){
var promise = $http.post("/myServiceUrl", { "myQuery": value.query });
promises.push(promise);
});
return $q.all(promises);

Categories

Resources