nilesh93
nilesh93

Reputation: 419

Angularjs for loop issue

There's a for loop and inside the for loop I'm calling an AJAX request. The issue I encountered is, the for loop finishes before the requests complete.

I want the for loop to continue to it's next iteration only after the required AJAX request completes.

PS- AJAX works fine. I do get my desired information from the server. It's just the for loop iterations complete first without waiting for the AJAX request success function to fire up. So when the AJAX success function finally fires the value in the variable cid is inconclusive as it has been overwritten by the last iteration of the for loop.

I want the for loop to continue only after the AJAX success function is executed.

Code:

if (window.cordova) {
    db = $cordovaSQLite.openDB("my.db"); //device
} else {
    db = window.openDatabase("my.db", '1', 'my', 1024 * 1024 * 100); // browser
}

var query = "SELECT * FROM order_product";
$cordovaSQLite.execute(db, query, []).then(function(res) {
    if (res.rows.length > 0) {
        for (var i = 0; i < res.rows.length; i++) {
            console.log(" foreach SELECTED shopcart-> " + res.rows.item(i).id);
            var cid = res.rows.item(i).coffee_id;
            $http.post("http://192.168.1.4/coffeepayWeb/public/index.php/getsugar", {
                cid: cID
            })
            .success(function(result) {
                console.log("success");

                if (cid == 6) {
                  //do something
                } else {
                  //do something else
                }
            });
        }
    }
}

Upvotes: 1

Views: 2398

Answers (2)

tavnab
tavnab

Reputation: 2734

$http uses promises, which means you need to think of the problem within the promise paradigm.

Consider a recursive option where you pass in an array of your cID's, and each call sends a $http.post for the 1st cID in the array; if the call succeeded, we continue recursively with a smaller array, until there are no more left.

One promise is created & returned in the 1st call, which is notified on each successful query (allowing you to do your per-cID logic), and finally resolved when all queries are done (or rejected if any query fails).

// This function is called without deferred;
// deferred is used on recursive calls inside the function
function doPost(url, cidList, deferred) {
  if (deferred === undefined) {
    deferred = $q.defer();
  }

  var cid = cidList[0];
  $http.post(url, {cid: cid})
  .success(function(result) {
     // query succeeded; notify the promise
     deferred.notify({cid: cid, result: result});

     if (cidList.length > 1) {
       // there are more items to process; make a recursive
       // call with cidList[1:end]
       doPost(url, cidList.slice(1), deferred);
     } else {
       // we're done; resolve the promise
       deferred.resolve();
     }
  })
  .error(function(message) {
    // there was an error; reject the promise
    deferred.reject({cid: cid, message: message});
  });

  return deferred.promise;
}

// build the list of cIDs to pass into doPost
var cidList = [];
for (var i = 0; i < res.rows.length; i++) {
  cidList.push(res.rows.item(i).coffee_id);
}

// start the queries
doPost("http://192.168.1.4/coffeepayWeb/public/index.php/getsugar", cidList)
.then(function() {
  // promise resolved
  console.log("All done!");
}, function(info) {
  // promise rejected
  console.log("Failed on cID " + info.cid + ": " + info.message);
}, function(info) {
  // promise being notified
  console.log("Just did cID " + info.cid + ": " + info.result);

  // your per-cid handler
  if (info.cid == 6) {
    // do something
  } else {
    // do something else
  }
});

UPDATE

Since the motivation for the question had more to do with variable scope (rather than sequential HTTP requests), this is all you really need:

// Build the CID list so that we can iterate it
var cidList = [];
for (var i = 0; i < res.rows.length; i++) {
  cidList.push(res.rows.item(i).coffee_id);
}

// Iterate the list & call $http.post
cidList.forEach(function(cid) {
  // your $http.post() logic; each call will have its own
  // cid thanks to closures
});

Each iteration will have it's own cid, which you can use in your .success() or .error() handlers without worrying about it being overwritten. As with the other solution, the requests aren't sequential, but you probably didn't need them to be in the first place.

Upvotes: 1

Leon Plata
Leon Plata

Reputation: 622

Using for is unsafe for asynchronous operations if you need the iteration index, use it only to store the required values to make the async operation ($http.post in this case), it should looks like:

var items = [];

for (var i = 0; i < res.rows.length; i++) {
  var item = res.rows.item(i);
  items.push(item);
}

after consider that $http returns a promise, then you should be able to map all elements from items

var getsugarUrl = 'http://192.168.1.4/coffeepayWeb/public/index.php/getsugar';

// Map the values to obtain the promises on each $http operation, the allSettled method of the
// [Kriskowal Q library](https://github.com/kriskowal/q/wiki/API-Reference) will be simulated
// this is because when one of the http requests fail then the $q.all method break and reject with an error
var promises = items.map(function (item) {
    var cid = item.coffee_id;
    return $http.post(getsugarUrl, { cid: cid })
      .then(function (result) {

        // You can take advantage of this closure to handle the result of the request an the
        // cid property, store your modified result on the value property from the return

        return {
          state: 'fullfilled',
          value: {
            result: result,
            cid: cid
          } // your modified result
        };
      })

      // Handle when the http request fails
      .catch(function (err) {
        return {
          state: 'rejected',
          error: err
        };
      });
  });

finally handle the results obtained using $q.all (you need to inject the $q service)

$q.all(promises)
  .then(function (responses) {
    // iterate over the results
    responses
      .filter(function(response) { // only fullfilled results
        return response.state == 'fullfilled'; 
      })
      .forEach(function (response) {
        if (response.value.cid == 6) {
          //do something with response.value.result
        } else {
          //do something else
        }
      });
  });

With this solution the http requests aren't resolved sequentially, but you have control over when they've finished together and you will have the correct value of cid

Check more about JavaScript Promises

Upvotes: 3

Related Questions