SB2055
SB2055

Reputation: 12862

Javascript forEach - waiting for setTimeout to complete before iterating

I have the following:

https://jsfiddle.net/qofbvuvs/

var allItems = ["1", "2", "3"]
var allPeople = ["A", "B"]

var testFoo = function(itemValue, peopleValue) {
  setTimeout(function(){
      return itemValue == "3" && peopleValue == "B"
  }, 200)
}

allItems.forEach(function(itemValue) {
  allPeople.forEach(function(peopleValue) {
    // I want to iterate through each object, completing testFoo before moving on to the next object, without recursion.  TestFoo has a few instances of setTimeout.  I've tried using promises to no avail.
    if (testFoo(itemValue, peopleValue)){
        alert("success")
    } else{
        // nothing
    };
  })
})
alert("complete")

My goal is to iterate through each item, one by one, in order, while waiting for the results of testFoo. If testFoo passes, then I should stop execution.

I've tried to use promises (https://jsfiddle.net/qofbvuvs/2/) but can't get the behavior I'm looking for. Success should be called before Complete. TestFoo has a couple setTimeouts that I need to work around (it's a library I can't modify). How can this be achieved?

Upvotes: 0

Views: 744

Answers (4)

Sasang
Sasang

Reputation: 1281

Since there's already a bunch of solutions out, I figured i'd add a solution using async/await. I personally like this one because it keeps the code similar to your original and more compact, where you don't have to keep track of any intermediate states or resolve all promises. The solution is just to wrap the for loops inside an async function and change the forEach to a basic for(var x...) because await wont work correctly within a forEach statement because you are defining an inner non async function and calling await within that is invalid. Additionally change the testFoo function to return a promise.

Since the for loops are inside a function you an easily exit it once a match has been found to skip further checks. I also added a return statement at the end of the loops to indicate nothing has been found, which can be handy.

Finally, since async functions themselves return a promise, all you have to do is add the final alerts inside the then of the returned promise to evaluate if a match has been found or not.

async/await example:

var allItems = ["1", "2", "3"]
var allPeople = ["A", "B"]

var testFoo = function(itemValue, peopleValue) {
   return new Promise(function(resolve, reject){
    setTimeout(function(){
        resolve(itemValue == "3" && peopleValue == "B");
    }, 200)
  });
}

async function checkItemPeople(){
  for(var itm=0; itm<allItems.length; itm++){
    for(var ppl=0; ppl<allPeople.length; ppl++){
      var result = await testFoo(allItems[itm], allPeople[ppl]);
      if (result) {
        alert("success");
        return true;
      } else {
        // nothing
      };
    }
  }
  return false;
}

checkItemPeople().then(function(resp){
  resp ? alert("Found Something") : alert("Theres nothing");
});

Upvotes: 1

mhodges
mhodges

Reputation: 11116

One way you can do it is through jQuery deferreds and manually stepping through your arrays, rather than using the built in loops, so you can control if/when you proceed. I guess it uses recursion in a way, but for nothing more than to invoke the next iteration - there is no crazy recursive return value unraveling or anything that makes recursion complex. Let me know if this works for you:

var allItems = ["1", "2", "3"]
var allPeople = ["A", "B"]

var testFoo = function(itemValue, peopleValue) {
  var deferredObject = $.Deferred();
  setTimeout(function() {
    deferredObject.resolve(itemValue == "3" && peopleValue == "B")
  }, 200)
  return deferredObject;
}
var currentItemIndex = 0;
var currentPeopleIndex = 0;

var testDeferred = $.Deferred();

function testAll() {
  testFoo(allItems[currentItemIndex], allPeople[currentPeopleIndex]).done(function(result) {
    if (result) {
      // found result - stop execution
      testDeferred.resolve("success");
    } else {
      currentPeopleIndex++;
      if (currentPeopleIndex >= allPeople.length) {
        currentPeopleIndex = 0;
        currentItemIndex++;
      }
      if (currentItemIndex >= allItems.length) {
        // result not found - stop execution
        testDeferred.resolve("fail");
      } else {
        // check next value pair
        testAll();
      }
    }
  });
  return testDeferred;
}

testAll().done(function resolveCallback (message) {
  alert(message);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Upvotes: 1

nem035
nem035

Reputation: 35491

If you want to construct a chain of promises in such a way where each promise isn't created until it's predecessor promise in the chain resolves, you should create an array of functions that return your promises and use reduction to create the needed promise chain.

var allItems = ["1", "2", "3"];
var allPeople = ["A", "B"];

// Async requests aren't made once we reach "2" and "B"
var resultToStopOn = function(itemValue, peopleValue) {
  return itemValue === "2" && peopleValue === "B"
}

var testFoo = function(itemValue, peopleValue) {
  return new Promise(function(resolve) {
    console.log('Starting promise for: ', itemValue, peopleValue);
    setTimeout(function() {
      console.log('Resolving promise for: ', itemValue, peopleValue);
      resolve(resultToStopOn(itemValue, peopleValue));
    }, 200);
  });
}

var functionsThatReturnPromises = [];
allItems.forEach(function(itemValue) {
  allPeople.forEach(function(peopleValue) {
    functionsThatReturnPromises.push(function() {
      return testFoo(itemValue, peopleValue);
    });
  });
});

functionsThatReturnPromises
  .reduce(function(chainOfPromises, fnReturningAPromise) {
    return chainOfPromises.then(function(result) {
      // if result is false, we continue down the chain
      // otherwise we just propagate the result and don't chain anymore
      return !result
        ? fnReturningAPromise()
        : result;
    });
  }, Promise.resolve())
  .then((result) => {
    console.log('Result: ', result);
  });

Upvotes: 0

ajai Jothi
ajai Jothi

Reputation: 2294

Used Promise.all to solve your problem. Hope this helps.

var allItems = ["1", "2", "3"];
var allPeople = ["A", "B"];

var testFoo = function(itemValue, peopleValue) {
  return new Promise(function(resolve) {
    setTimeout(function() {
      resolve(itemValue === "3" && peopleValue === "B");
    }, 200);
  });
}

var promises = [];
allItems.forEach(function(itemValue) {
  allPeople.forEach(function(peopleValue) {
    promises.push(testFoo(itemValue, peopleValue).then(function(result) {
      console.log(result?'success':'fail');
      return result;
    }));
  })
});

Promise.all(promises).then(function(results) {
  console.log('Result from all promise: ' + results);
  console.log("complete");
});

Upvotes: 0

Related Questions