thdoan
thdoan

Reputation: 19107

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.

Upvotes: 0

Views: 107

Answers (5)

guest271314
guest271314

Reputation: 1

You can create a function which includes current for, for..in loop pattern, though iterating only single a element at for loop a function call while maintaining existing for..in loop; substitute returning a Promise which recursively calls function if incremented variable is less than a.length; else recursively return function with incremented variable, or if variable is a.length, return a value to .then() at completion of process.

This approach will make calls to setTimeout() sequential, without generating a pending call to setTimeout in future, next call to function is only made when current setTimeout completes, or no matches were returned for that iteration of loops.

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];
}

function matcher(index = 0) {
  var len = index < a.length;
  for (var i = index; i < index + 1; ++i) {
    for (var j in o) {
      if (matched(i, j)) {
        return new Promise(function(resolve) {
          setTimeout(function() {
            resolve(a[i]);
          }, 1000)
        })
        .then(function(result) {
          console.log(`result:${result}`);
          if (len) {
            return matcher(++index)
          }
        });
      }
    }
  };
  return len ? matcher(++index) : "matcher complete";
}

matcher().then(function(complete) {
  console.log(complete);
});

Upvotes: -1

Nina Scholz
Nina Scholz

Reputation: 386680

An ES6 solution with a generator.

function* getCommon(array, object) {
    var set = new Set(array), k;
    for (k in o) {
        if (set.has(o[k])) {
            yield o[k];
        }
    };
}

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 },
    generator = getCommon(a, o),
    interval = setInterval(function () {
        var item = generator.next();
        if (item.done) {
            clearInterval(interval);
        } else {
            console.log(item.value);
        }
    }, 1000);

Upvotes: 1

T.J. Crowder
T.J. Crowder

Reputation: 1074959

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.

Upvotes: 3

Marcus
Marcus

Reputation: 1930

Take you code but change the output to a push into another (global) array. Then use a timer to print the array content one by one.

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},
  t = [];

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

// function to output first element of array
function output(){
  if(t.length > 0){ // only generate output if array t isn't empty
    console.log(t.shift());
    setTimeout(output, 1000); // recall this function after 1s
  }
}

for (var i=0; i<a.length; ++i) {
  for (var j in o) {
    if (matched(i, j)) t.push(a[i]); // store all found items inside the new array
  }
}
output();

Upvotes: 1

Cerbrus
Cerbrus

Reputation: 72927

The solution is relatively simple:

Don't worry about the logging when you're gathering the results. Instead, store all results in a new array.

Then, iterate over that result array, with a delay:

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},
    result = [];

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))
      result.push(a[i]); // Store the found result.
  }
}

var i = 0,
    length = result.length;

(function iterator() {
    console.log(result[i]);         // Log the current result

    if(++i < length) {              // If there are more entries in the array
        setTimeout(iterator, 1000); // Log the next entry in 1 second.
    }
})();

Upvotes: 2

Related Questions