I’ve spent the last few days trying to tackle this issue and have read all sorts of solutions on StackOverflow and other sites.
I’m building a site that grabs XML data from an outside source and then gets more XML depending on the results to build a network graph. The problem is that I have to essentially wait until this loop of AJAX calls (which may loop into more AJAX calls) is finished before drawing.
I don’t know if this just has an especially high cognitive load, but it really has me stumped.
My Code:
function cont(outerNodes) {
for (var i = 0; i < outerNodes.length; i++) {
var node = outerNodes.pop();
getXML(node["label"], node["id"]);
}
// I want the code to wait until loop is done, and then draw.
draw(nodes, edges);
}
function getXML(term, fromId) {
var url = someURL;
$.ajax({
url: url,
dataType: "xml",
success: function(result) {
var outerNodes = process(result, fromId, term);
cont(outerNodes);
}
});
}
Note: I understand I may be completely misunderstanding JavaScript synchronicity here, and I very likely am. I have used callbacks and promises successfully in the past, I just can’t seem to wrap my head around this one.
If I have not been totally clear, please let me know.
I did try implementing a counter of sorts that is incremented in the process() function, like so:
if (processCount < 15) {
for (var i = 0; i < outerNodes.length; i++) {
var node = outerNodes.pop();
getXML(node["label"], node["id"]);
}
} else {
draw(nodes, edges);
}
However, this ended up with several draw() calls which made my performance abysmal.
4
Answers
If after testing it many times, you know that it will never take more than say 5 seconds… you can use a setTimeout.
The best way to solve this question should be using either promise or callback.
If you really want to avoid promise or callback(Although i don’t know why…)
You can try with a counter.
There are nice new well-supported APIs and language constructs we can use. The Fetch API,
await
, andfor...of
loops.The Fetch API uses Promises. Promises can be awaited. The
for...of
loop is aware ofawait
and won’t continue the loop until theawait
has passed.Don’t forget a
try
/catch
(which also works withawait
), and checkres.ok
.Brad’s answer changes the code to by synchronious and to me that defeats the purpose. If you are constantly waiting on all request to be finished then it could take a while, while normal browsers can handle multiple requests.
The problem you have in your original questions is with scope. Since each call to
cont(outerNodes)
will trigger it’s own scope, it has no idea what are calls are doing. So basically if you callcont(outerNodes)
twice, each call will handle it’s own list ofouternodes
and then calldraw
.The solution is to share information between the different scopes, which can be done with one, but preferably two global variables: 1 to track active processes and 1 to track errors.
Please note that I track
nrErrors
but dont do anything with it. You could use it to stop further processing all together or warn the user that the draw is not complete.[important] Keep in mind that this works in
javascript
because at best it mimics multithreading. That means the the call toinProcess--;
and then right aftercont(outerNodes);
is always execute directly after eachother.If you would port this to a true multithreading environment, it could very well be that another scope/version of
cont(null);
would cut in between the two lines and there would still be multiple draws.