Search code examples
javascriptangularjsdeferredangular-promise

Recursive function with defer


I want to use a recursive function but each function should run after previuse complete. so I write this code:

var service = ['users', 'news'],
    lastSync = {
                 'users' : false,
                 'news' : false
               };
db.transaction(function (tx) {
   lastSyncFunc(tx,service,0).then(function(){
       console.log(lastSync);
   });
});

function lastSyncFunc(tx,service,index){
   deferred = $q.defer();
   tx.executeSql("SELECT time FROM last_sync WHERE fService = ? ORDER BY id DESC LIMIT 1", [service[index]], function (tx, result) {
       if (result.rows.length > 0) {
           lastSync[service[index]] = result.rows.item(0).fTime;
       }
       return ++index<service.length ? lastSyncFunc(tx,service,index) : deferred.resolve();
   });
   return deferred.promise;
}

now my program return false for lastSync.users and lastSync.users because run this section before function completely run.


Solution

  • Manual multiple async calls handling is not always best decision.
    You can try to use $q.all() for that.

    To be simple, second step should be write promisified version for single query:

    const pDbExec = (tx, sql, params = []) => {
      let deferred = $q.defer();
      tx.executeSql(sql, params, (tx, res) => deferred.resolve(res));
      return deferred.promise();
    }
    

    First step should be "check of existance of promisified version of library/methods I use".

    Then, just call $q.all with map your service list into promises:

    const SQL_SvcLastSync = `SELECT time FROM ... LIMIT 1`;
    db.transaction(tx => {
      $q.all(service.map(svc => pDbExec(tx, SQL_SvcLastSync, [svc])))
        .then(results => 
           results.map(res => 
             res.rows.length > 0 ? res.rows.item(0).fTime : null))
        .then(results => console.log(results));
    });
    

    To format results as key/value pairs you have two options:

    • Add reducer: .then(results => results.reduce((acc, res, i) => (acc[service[i]]=res, acc), {}))
    • provide key/value(where values are promises) instead of array map to $q.all, so they will be resolved under same keys.
      You'll need to modify intermediate mapper, sure.

    Simple solution with adding parameter for saving same deferred object between recursive calls:

    try something like this:

    function lastSyncFunc(tx,service,index, def){
       var deferred = def || $q.defer();
       tx.executeSql(
         "SELECT time FROM last_sync WHERE fService = ? ORDER BY id DESC LIMIT 1", 
         [service[index]], 
         function (tx, result) {
           if (result.rows.length > 0) {
               lastSync[service[index]] = result.rows.item(0).fTime;
           }
           return ++index<service.length ? 
             lastSyncFunc(tx,service,index, deferred) : 
             deferred.resolve();
       });
       return deferred.promise;
    }
    

    I just a provide deferred to maxdepth, where we can to resolve it.