10basetom 10basetom - 2 months ago 9
Javascript Question

JavaScript delay when true in inner loop

I have a situation that so far I have not been able to find a satisfactory solution for. Below is the code on a high level.

var a = [1, 2, 3, 4, 5, 6, 7, 8, 9],
o = {a:1, b:2, c:3, d:10, e:11, f:12, g:7, h:8, i:9};

function matched(i, j) {
return a[i]===o[j];
}

for (var i=0; i<a.length; ++i) {
for (var j in o) {
if (matched(i, j)) console.log(a[i]);
}
}


I have an array and an object. I'm looping through the array, then the object, to find a match via the function
matched()
which returns a boolean
true
or
false
. If the condition is
true
, then I log the array item. If you run the code right now (https://jsfiddle.net/thdoan/0tubbokj/), you should see numbers 1-3 and 7-9 outputted to the console.

What I'm trying to do is to output the numbers with a one-second delay in between each number. I know how to introduce a delay in between each loop iteration, but I only want to add the delay for the numbers that are printed (i.e., when
matched()
returns
true
).

Clarification: My current solution, which I'm not satisfied with, is to save the matched items to a separate array and iterate over that array with a delay, but I'm looking for a solution that does not require creating a new array.

Answer

What I'm trying to do is to output the numbers with a one-second delay in between each number.

You've also said in a comment:

...in the real app the matched set can grow to be very large, so I would rather not consume more memory if there is a solution that doesn't require outputting to a third array.

To achieve both of those goals, you have to completely abandon your for loops and instead do a chained series of setTimeouts.

var a = [1, 2, 3, 4, 5, 6, 7, 8, 9],
  o = {a:1, b:2, c:3, d:10, e:11, f:12, g:7, h:8, i:9};

function matched(i, j) {
  return a[i]===o[j];
}

// Get the property names in `o`, and start at the beginning
var keys = Object.keys(o);
var i = 0;
var keyIndex = 0;
tick();

function tick() {
  // Get the "j" value for this tick
  var j = keys[keyIndex];
  
  // Is this a match?
  var flag = matched(i, j);
  if (flag) {
    console.log(a[i]);
  }
  
  // Move to the next entry in our nested loops
  if (++keyIndex >= keys.length) {
    keyIndex = 0;
    if (++i >= a.length) {
      // Done
      return;
    }
  }

  // Continue
  if (flag) {
    // We output one, wait a second before next
    setTimeout(tick, 1000);
  } else {
    // No output, continue immediately
    tick(); // SEE NOTE BELOW
  }
}

NOTE: If there may be thousands of non-matches in a row, you might consider using a loop in tick instead of chaining to it. As of ES2015, JavaScript is supposed to have tail-call optimization and our tick wouldn't have to push itself on the stack (it would just call back to the beginning), but some JavaScript engines haven't implemented TCO yet, which could mean you'd end up with a significant stack depth.

So, with a loop:

var a = [1, 2, 3, 4, 5, 6, 7, 8, 9],
  o = {a:1, b:2, c:3, d:10, e:11, f:12, g:7, h:8, i:9};

function matched(i, j) {
  return a[i]===o[j];
}

// Get the property names in `o`, and start at the beginning
var keys = Object.keys(o);
var i = 0;
var keyIndex = 0;
tick();

function tick() {
  var match = findNextMatch();
  if (match) {
    console.log(match);
    setTimeout(tick, 1000);
  }
}

function findNextMatch() {
  var j;
  var match;
  
  while (!match && i < a.length) {
    j = keys[keyIndex];
    if (matched(i, j)) {
      match = a[i];
    }

    // Move to the next entry in our nested loops
    if (++keyIndex >= keys.length) {
      keyIndex = 0;
      ++i;
    }
  }

  return match;
}

Actually, that seems cleaner to me anyway, even without the deep stack concern.