0

I have two functions periodically called via setInterval. The goal is to defer Function B until Function A is done (and vis versa). Currently, Function A will start, complete some of its subroutines, but not reach the end before Function B begins.

I've tried passing Function B as an argument of Function A. I am not sure if that was sufficient to create a callback. I also tried jQuery's $.when(setInterval(functionA, 10000)).then(setInterval(functionB, 5000)).

How do I ask JavaScript to wait for functions/blocks of code to finish? Thank you in advance.


Edit: Below is code very similar to my original. Sorry for not being concise.

Function A, getFruits(): There is a remote JSON that changes on its own (fruits.json). getFruits() does two things: 1) It empties an array, [allFruits] (just in case); 2) It adds all the names of fruit currently in the remote JSON to [allFruits]. Now, [allFruits] is an instanced copy of the remote JSON. Before this question, I only called getFruits() once, at startup; in other words, I did not use setInterval for getFruits().

Function B, checkFruits(): Now checkFruits() periodically (setInterval(checkFruits, 5000)) compares [allFruits] to the remote version. If any fruit was added to the remote version, checkFruits appends [allFruits] with those fruits' names; it also runs useful code (i.e. pushes the new names to an array [queue]).

For this implementation, it is important to create an initial list so only new (post-startup) fruit trigger the useful code of checkFruits(). Moreover, it is important only to add (never subtract) names from [allFruits] within a session. This is to prevent a new fruit from triggering the useful code more than once per session.

Problem: Now I want to make getFruits() (Function A) periodic. Because getFruits() empties [allFruits], it will allow the names that built up to again trigger useful code (but only once in between invocations of getFruits()). However, when I use setInterval(getFruits, 10000), there are times (in this example, always) when getFruits() overlaps with checkFruits(). When that happens, I notice only part of getFruits() finishes before checkFruits() starts. The console.log() messages appear in this order: 'getFruits() start:', 'checkFruits():', 'getFruits() end:'. Furthermore, my useful code is ran before getFruits() finishes (this is what is really undesired), and [allFruits] gets duplicates. This would not occur if getFruits() completely finished before checkFruits() jumped in.

debugging = true;

var debug = function() {
 if (debugging){
  console.log.apply(console, arguments)
 };
}

var allFruits = [];
var queue = [];

var getFruits = function() {
 allFruits = []; // Empty the list
 debug('getFruits() start:', 'allFruits =', allFruits, 'queue =', queue);
 $.ajax({
  url: 'fruits.json',
  dataType: 'json',
  success: function(data) {
   data.fruits.forEach(function(element) {
    allFruits.push(element.name);
   });
   debug('getFruits() end:', 'data =', data, 'allFruits =', allFruits, 'queue =', queue);
  },
 });
}

var checkFruits = function() {
 $.ajax({
  url: 'fruits.json',
  dataType: 'json',
  success: function(data) {
   data.fruits.forEach(function(element) {
    if (allFruits.indexOf(element.name) === -1) {
     queue.push(['fruit', element.name]);
     allFruits.push(element.name);
    }
   });
   debug('checkFruits():', 'data =', data, 'allFruits =', allFruits, 'queue =', queue); 
  }
 });
}

getFruits();
setInterval(checkFruits, 5000);
// setInterval(getFruits, 10000); // When I try this, checkFruits() does not wait for getFruits() to finish.

The analogy of my actual remote resource is fruits.json. fruits.json can simply be the following: {"fruits":[{"name":"apple","color":"red"},{"name":"banana","color":"yellow"},{"name":"tangerine","color":"orange"}]}

Again, the actual, remote JSON changes independently.

Polite Master
  • 125
  • 1
  • 9
  • Why do you want to delay the `functionA` to not reach the end of the function before `functionB` is finished? Is there some part of `functionA` that needs to complete after the `functionB` is finished? – jehna1 Mar 12 '16 at 21:49
  • Hi Jehna1. It's the opposite. I want Function A to reach the end before Function B starts. The status quo is this isn't happening. Nothing in Function A needs to come after Function B. :) – Polite Master Mar 12 '16 at 21:52
  • Then why dont you juste call function B at the end of your function A – Baldráni Mar 12 '16 at 22:04
  • I'd like to get a better understanding of your problem. You say that Function B begins execution before Function A is finished. Are the internals of Function A asynchronous? If not I'm not convinced this is possible since JavaScript is single threaded. Two methods cannot be running simultaneously. – alaney Mar 12 '16 at 22:11
  • Thanks @Baldráni. I don't think I can in this case, because the two functions need to be called at different frequencies. The problem happens when they are called at the same time, or close to each other, such that one of the functions is not done yet. – Polite Master Mar 12 '16 at 22:12
  • Hi @RodgerTheGreat! I have seen 'asynchronous' thrown around, but I don't know what you mean as a novice programmer. I use console.log() to print stuff at 2 steps in Function A. Because Function A and Function B are invoked every 10 sec and 5 sec, respectively, they overlap. When they overlap, Function A will start, print the first message, and then Function B will start (again, letting me know via console). Finally Function A lets me know it finished. This is not desired. I would like both console.log() messages to print (have the entirety of Function A execute) before Function B starts. – Polite Master Mar 12 '16 at 22:18
  • I think it would be beneficial to see your full implementation, that is, the code for both functions. Otherwise, I'm not sure anyone will be able to help you. – alaney Mar 12 '16 at 22:21
  • Sure thing. I should probably add an example instead of posting the unabridged code. I will amend my question when ready. – Polite Master Mar 12 '16 at 22:26

4 Answers4

0

your last code example first executes setInterval(functionA), and when the deferred execution of functionA is setup, executes setInterval(functionB), meaning that B will called +- 5 seconds after that line is executed, while functionA is called +- 10 seconds.

edit to reflect your additional information:

setInterval(function(){
   functionA();
   functionB();
}, 10000)

setTimeout(function(){
    setInterval(functionB, 10000)
}, 5000)
Matthijs Brouns
  • 2,299
  • 1
  • 27
  • 37
  • Thanks for the answer, Matthijs, and apologies for lack of clarity (I am a novice). I need Function A and Function B to be on separate timers. Let's say Function A runs every 10 seconds and Function B every 5 seconds. If (when) these functions are called at/close to the same time, Function A needs to completely finish before Function B can start. – Polite Master Mar 12 '16 at 21:58
  • In that case I would set two timers, one that starts at t=0 and repeats every 10 seconds which performs first functionA then functionB, and another timer that starts at t=5 and repeats every 10 seconds that only runs functionB. I edited my answer to the use case – Matthijs Brouns Mar 13 '16 at 09:51
  • Apologies Matthijs; I missed your additions. I am a little confused by the second block. The first clearly repeats Function A and Function B every 10 seconds. I know `setTimeout` runs a function once, but the `setTimeout` function includes a `setInterval`. – Polite Master Mar 14 '16 at 20:02
  • the second block will run functionB every 10 seconds (the setInterval part) and will be started after 5 seconds (the setTimeout part). This means that T=5, setInterval(functionB, 10000) will run, scheduling another run of functionB at T=15 and so on – Matthijs Brouns Mar 14 '16 at 20:13
  • Ah that makes sense! I appreciate your idea and will add it to my coding vocabulary. Unfortunately, it is not enough. In this case, Function A and Function B include asynchronous subroutines ($.ajax). At T=10, T=20, etc., the _synchronous_ part of Function A will finish, then Function B will start, and finally the rest of Function A finishes. My question is how to get Function A (with a JSON call) to completely finish before Function B is allowed to start. – Polite Master Mar 14 '16 at 21:45
0

What you have here are two methods that each do asynchronouse stuff. Here are some good stack overflow posts on what that means.

Easy to understand definition of "asynchronous event"?

Does async programming mean multi-threading?

Are JavaScript functions asynchronous?

We have no idea how long it will take for an asynchronous call to finish. In your case, the AJAX request could take up to a few seconds depending on network speeds so regardless of when each of these methods are executed you CANNOT know which one will finish first. So what to do? Well, generally when you write/use an asynchronous method (like $.ajax) you give it a callback that will be executed when the asynchronous work is finished. And you have done this in the form of the success callback. And here is the good news. The success callbacks are SYNCHRONOUS (note the missing a). This means that the "useful code" in the success callback that needs to be run when a request finishes will complete (so long as none of it is async) before the "other useful code" in the other success callback is executed at all. And this works no matter which request finishes first. Each success callback will always wait for the other. So I think what was confusing you was your debug statements. If you add the following statements to your code the execution flow may make more sense:

debugging = true;

var debug = function() {
  if (debugging) {
    console.log.apply(console, arguments)
  };
}

var allFruits = [];
var queue = [];

var getFruits = function() {
  debug("getFruits: make request");
  $.ajax({
    url: 'fruits.json',
    dataType: 'json',
    success: function(data) {
      debug("getFruits: start processing");
      allFruits = []; // Empty the list
      data.fruits.forEach(function(element) {
        allFruits.push(element.name);
      });
      debug('getFruits: finished processing');
    },
  });
  debug("getFruits: request sent, now we wait for a response.");
}

var checkFruits = function() {
  debug("checkFruits: make request");
  $.ajax({
    url: 'fruits.json',
    dataType: 'json',
    success: function(data) {
      debug("checkFruits: start processing");
      data.fruits.forEach(function(element) {
        if (allFruits.indexOf(element.name) === -1) {
          queue.push(['fruit', element.name]);
          allFruits.push(element.name);
        }
      });
      debug("checkFruits: finished processing");
    }
  });
  debug("checkFruits: request sent, now we wait for a response.");
}

getFruits();
setInterval(checkFruits, 5000);
// setInterval(getFruits, 10000); // When I try this, checkFruits() does not wait for getFruits() to finish.

After thinking about it I believe the only reason things may not have been behaving as expected is because you're emptying the allFruits array outside of the callback. If you move it as I have done I would think everything should work fine.

Now, I don't know why you need to re-initialize the data since each time you make the request your getting the latest information but lets roll with it. Since both methods make the same request lets consolidate that into a single method. No need to duplicate code ;). And since all of your examples have the getFruits running twice as slow as the checkFruits we could easily add a counter to accomplish the same sequence of events like so:

    debugging = true;

    var debug = function() {
      if (debugging) {
        console.log.apply(console, arguments)
      };
    }

    var allFruits = [];
    var queue = [];
    var count = 0;

    var doOneThing = function(data) {
       //do stuff
    }
    
    var doAnotherThing= function(data) {
       //do other stuff
    }

    var requestFruits = function() {
      $.ajax({
        url: 'fruits.json',
        dataType: 'json',
        success: function(data) {
          // if count is even...or, do this every other time.
          if (count % 2 === 0) {
            count++;
            doOneThing(data);
          }
          // do this everytime
          doAnotherThing(data);
        },
      });
    }

    setInterval(requestFruits, 5000);

Hope this helps. Cheers.

Community
  • 1
  • 1
alaney
  • 613
  • 5
  • 12
  • I appreciate the help, RogerTheGreat. I was able to understand asynchronous flow (made a lot of sense with this experience) and tried the counter to make my code more efficient. :) Unfortunately, simply moving [allFruits] within the the async JSON call was not effective. I have an answer below, although it feels crude. – Polite Master Mar 14 '16 at 03:09
  • @RogerTheGreat In response to "Now, I don't know why you need to re-initialize the data...": [allFruits] is a blacklist. Say Kiwi shows up on the remote JSON. Kiwi triggers useful code once and is blacklisted from doing so again. After a period of time, I'd like to clear the blacklist. However, I can't just empty [allFruits]. There always needs to be an initial list, because I only want new, incoming fruit to trigger useful code. Without an initial list, simply all the fruit in the remote JSON will trigger the code. – Polite Master Mar 14 '16 at 03:09
  • I should also note that using a counter is very helpful for *avoiding overlap*. Let's say I put getFruits() and checkFruits() into if/else clauses of a new function everything() that also increments counter by 1. I call everything() every 5 seconds with `setInterval`. It runs getFruits() when `counter % 2 === 0` (T=10, T=20, ...) and checkFruits() every other time (T=5, T=15, ... which is not every 5 seconds, but acceptable). Great! However, I need to be **absolutely sure** getFruits() finishes before the next checkFruits() in 5 seconds. If I can be, this implementation is excellent. – Polite Master Mar 15 '16 at 00:26
0

This is a crude answer. I sense that callbacks can achieve this, but I am not sure how to code them, especially involving setInterval.

I create two global variables, getFruitsIsBusy = false and checkFruitsIsBusy = false. I create an IF for both getFruits() and checkFruits(). Here is getFruits():

var getFruits = function() {
 if (checkFruitsIsBusy) { // New
  setTimeout(getFruits, 100); // New
  return; // New
 
 } else { // New
  getFruitsIsBusy = true // New
  
  allFruits = []; // Empty the list
  debug('getFruits() start:', 'allFruits =', allFruits, 'queue =', queue);
  $.ajax({
   url: 'fruits.json',
   dataType: 'json',
   success: function(data) {
    data.fruits.forEach(function(element) {
     allFruits.push(element.name);
    });
    getFruitsIsBusy = false // New; in the success function
    debug('getFruits() end:', 'data =', data, 'allFruits =', allFruits, 'queue =', queue)
   },
  });
 }
}

If also using this paradigm for checkFruits(), it seems both functions will wait for each other to finish.

Polite Master
  • 125
  • 1
  • 9
  • This has a very bad code smell to me. I suppose someone should have asked...Other than the functions not waiting for one another, what is happening that is undesired? That is, what is not working properly. Are you getting some errors? – alaney Mar 14 '16 at 13:24
  • My actual code is an alert app for Twitch.tv. The fruit are the names of people that host (and follow) a channel. When a user hosts your channel, they are pushed to the queue, and an alert is displayed on screen (via JS + HTML/CSS); this is the useful code. Sometimes, a user will host, unhost, and rehost you. I want to allow a second alert to appear if that happens after an hour (in testing, 10000 ms). (Continued...) – Polite Master Mar 14 '16 at 20:30
  • Continuation: The JS in my question with `setInterval(getFruits, 10000)` uncommented, as well as your first suggestion, results in my current (unchanging) hosts to display alerts infinitely. getFruits() empties [allFruits], then checkFruits() displays alerts for everyone (because no one is on the blacklist, though should be), and finally getFruits() fills [allFruits]. I need the block of code getFruits() to finish first. Another ugly side effect is [allFruits] fills with a single, duplicate set of all my hosts (although this will not affect the actual alerts). No errors, by the way. – Polite Master Mar 14 '16 at 21:19
  • Control flow: 1) At startup, check current hosts and blacklist them. 2) Periodically check for new hosts (anyone not blacklisted). 3) If there is a new host, push them to the queue once (resulting in an alert) and blacklist (can't alert for a while). 4) _In between_ two checks, clear blacklist (on the next check, all current hosts would go to queue again; bad) _and_ blacklist the current hosts as in step 1 (now good). Before step 4, the blacklist includes all users currently hosting and old ones no longer hosting. Step 4 removes the old ones, but it all needs to be done before the next check. – Polite Master Mar 14 '16 at 23:51
0

Based on an analysis of the timing of two functions (A and B), consider the following solution (Chionglo, 2016):

  1. Keep state information for each of function A and function B. The state of each function should be set within each of the respective functions.
  2. Create a wrapper function for each of function A and function B. The wrapper function calls on the respective function, and then checks for the state of the respective function.

    a. The check in wrapper function A: if function A has reached is final state, clear the interval associated with wrapper function A and schedule an interval for wrapper function B.

    b. The check in wrapper function B: if function B has reached its final state, clear the interval associated with wrapper function B.

  3. To begin the process, schedule an interval for wrapper function A.

Sample code:

var ac = Math.round(4*Math.random())+4;
var bc = Math.round(6*Math.random())+6;
var ai;
var Astate = false;
var Bstate = false;

function A() {
  // Do your thing for A here.
  // The following changes the “state of A” and then determines if the final state has been reached.
  ac -= 1;
  if (ac<1) Astate = true;
  else Astate = false;
}

function B() {
  // Do your thing for B here.
  // The following changes the “state of B” and then determines if the final state has been reached.
  bc -= 1;
  if (bc<1) Bstate = true;
  else Bstate = false;
}

ai = setInterval("processA()", 1000);

function processA() {
  A();
  if (Astate) {
    clearInterval(ai);
    ai = setInterval("processB()", 500);
  }
}

function processB() {
  B();
  if (Bstate) {
    clearInterval(ai);
    ai = undefined;
  }
}

Reference

Chionglo, J. F. (2016). An analysis for timing a set of functions. Available at http://www.aespen.ca/AEnswers/1458200332.pdf.