How to avoid nested call in ionic / angular?

I am completely new to ionic / angular, this is my code:

.controller('PostCtrl', function($scope, Posts, $cordovaSQLite, $http) {
  $scope.getPosts = function() {
    $http.get('http://localhost/postIds').then(function(resp) {
      _.each(resp.data, function(id) {
        var query = "SELECT id FROM posts WHERE id = ?";
        $cordovaSQLite.execute(db, query, [id]).then(function(res) {
          if(res.rows.length = 0) {
            $http.get('http://localhost/post/' + id).then(function(resp) {
              var post = resp.data;
              var query = "INSERT INTO posts (postId, title, user, content) VALUES (?,?,?,?)";
              $cordovaSQLite.execute(db, query, [post.id, post.title, post.user, post.content]).then(function(res) {
                // success
              }, function(err) {
                console.log(err);
              });
            }, function(err) {
              console.log(err);
            });
          }
        }, function (err) {
          console.error(err);
        });
      });
    }, function(err) {
      console.log(err);
    });
  }
})

      

What am I doing,

  • get all ids from server

  • If id doesn't exist in db (sqlite)

  • get a message by id from the server

  • Insert message into db

It ends up being deeply nested, ugly.

what is the ionic, angular way of doing this?

+3


source to share


1 answer


Like others have suggested, the best option is to use promises, so you don't need to insert assertions as you do.

AngularJs uses $ q promises :

A service that helps you run functions asynchronously and use them to return values ​​(or exceptions) when they are executed.

There are many articles on the internet about promises and how to bind them.
I recently found this article which explains common mistakes using promises.
It's worth reading because it goes deeper into this topic.

In AngularJs, you will create a promise using a service $q

:

function doSomething() {
   var deferred = $q.defer();
   deferred.resolve({value: true});
   return deferred.promise;
}

      

This bit of code returns a promise which is allowed - since there is no async operation when it is called. It will return an object with property value

= true.
The cool thing about promises is that you can chain them:

doSomething()
  .then(function(result){
      // result.value should be true.
      return doSomething();
  })
  .then(function(result){
      // result.value should be true.
      // this is the result of the second call.
  });

      

transferring the result according to the previous - resolved promise.

If promises are rejected due to some exception:

deferred.reject({value: false});

      

you can catch the error and stop execution on the chain:

doSomething()
  .then(function(result){
      // result.value should be true.
      return doSomething();
  })
  .then(function(result){
      // result.value should be true.
      // this is the result of the second call.
  })
  .catch(function(reason){
      // reason for failure.
  });

      

Finally, you can use finally

for cleaning or other things:

doSomething()
  .then(function(result){
      // result.value should be true.
      return doSomething();
  })
  .then(function(result){
      // result.value should be true.
      // this is the result of the second call.
  })
  .catch(function(reason){
      // reason for failure.
  })
  .finally(function(){
      // it going to be executed at the end of the chain, even in case of error trapped by the catch.
  });

      

The whole thing is not so simple. At first, you can spend a few hours debugging your code.

How can I fix your code?

First of all, I would create a function that retrieves the ids that call the web api:

function fetchIds() {

    console.log('Fetching Ids ...');

    var deferred = $q.defer();

    $http({
        method: 'GET',
        url: 'http://localhost/postIds',
        params: {}
    })
    .success(function(data) {
        deferred.resolve(data);
    })
    .error(function(data, status) {
        deferred.reject(data);
    });

    return deferred.promise;
}

      

As you can see, I have implemented the system described above. $http

already returns a promise, but I wrapped it in a new promise.



Then I would have to query the database to find non-existing IDs (I did not put my code in a loop, as it is easier to get all the records in one call):

function queryForIds(ids) {

    console.log('Querying for Ids ' + ids.toString() + ' ...');

    var deferred = $q.defer();

    var params = [];
    for (var i = 0; i < ids.length; i++) {
        params.push('?');
    }

    window.myDatabase.transaction(function(tx) {
       tx.executeSql("SELECT * FROM posts WHERE postId IN (" + params.join(',') + ")", ids,
           function(tx, results) {
              deferred.resolve(results.rows);
           },
           function(tx, reason) {
              deferred.reject(reason);
           });
    });

    return deferred.promise;
}

      

My code will be slightly different from yours as I was using WebSql because I wanted to test it in a browser.

Now we need to find identifiers that don't exist in the db:

function getNonExistingIds(ids, dbData) {

    console.log('Checking if Ids ' + ids.toString() + ' exist in the db ...');

    if (!ids || ids.length === 0) {
        console.log('No ids');
        return [];
    }

    if (!dbData || dbData.length === 0) {
        console.log('database is empty');
        return ids;
    }

    var dbIds = [];
    angular.forEach(dbData, function(data, key) {
        dbIds.push(data.postId);
    });

    var nonExisting = [];

    angular.forEach(ids, function(id, key) {
        var found = $filter('filter')(dbIds, id, true);
        if (found.length === 0) {
            nonExisting.push(id);
        }
    });

    return nonExisting;
}

      

This function does not return a promise, but you can still broadcast it as if you were doing a real promise (you will learn how later).

Now we need to call the web api to fetch messages for ids that cannot be found in the database:

function fetchNonExisting(ids) {

    if (!ids || ids.length === 0) {
        console.log('No posts to fetch!');
        return;
    }

    console.log('Fetching non existing posts by id: ' + ids.toString() + ' ...');

    var promises = [];

    angular.forEach(ids, function(id, key) {
        var promise = $http({
            method: 'GET',
            url: 'http://localhost/post/' + id,
            params: {}
        });
        promises.push(promise);
    });

    return $q.all(promises);
}

      

Here are interesting.

Since I want this function to return one and only result with an array of messages, I created an array of promises. The $ http service is already returning a promise. I am pushing it into an array.
At the end, I am trying to resolve the promises array with $q.all

. Really cool!

Now we need to write the messages loaded into the database.

function writePosts(posts) {

    if (!posts || posts.length === 0)
    {
        console.log('No posts to write to database!');
        return false;
    }

    console.log('Writing posts ...');

    var promises = [];

    angular.forEach(posts, function(post, key) {
        promises.push(writePost(post.data));
    });

    return $q.all(promises);
}

      

Again, we are chaining an array of promises so that we can resolve them all in one go. This function here calls writePost

:

function writePost(post) {
    return $q(function(resolve, reject) {
        window.myDatabase.transaction(function(tx) {
            tx.executeSql("INSERT INTO posts (postId, title, user, content) VALUES (?,?,?,?)", [post.id, post.title, post.user, post.content],
                function(tx, result) {
                    console.log('INSERT result: ' + result);
                    resolve(result);
                },
                function(tx, reason) {
                    console.log('INSERT failure: ' + reason);
                    reject(reason);
                });
        });
    });
}

      

this bit is pretty tricky here because WebSql doesn't work with promises and I want them to be resolved in one go and return a result.

Now, what can you do with all these features? Well, you can link them as I explained earlier:

var ids = [];

fetchIds()
    .then(function(data) {
        console.log(data);
        ids = data;
        return queryForIds(data);
    })
    .then(function(dbData) {
        return getNonExistingIds(ids, dbData);
    })
    .then(function(nonExistingIds) {
        console.log('Non existing ids: ' + nonExistingIds);
        return fetchNonExisting(nonExistingIds);
    })
    .then(function(response) {
        return writePosts(response);
    })
    .then(function(result) {
        console.log('final result: ' + result);
    })
    .catch(function(reason) {
        console.log('pipe error: ' + reason);
    })
    .finally(function() {
        // Always Executed.
    });

      

The final result can be found in this gist .

If you want to download the entire application and test it on your PC, this is the link ( myApp ).

+3


source







All Articles