How to avoid nested call in ionic/angular?

Sato

I'm totally 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 is

  1. get all ids from server

  2. if id doesnt exist in db(sqlite)

  3. get post by id from server

  4. insert post into db

It ends up deeply nested, ugly.

what is the ionic, angular way to do this?

LeftyX

As the others suggested the best option is to use promises so you don't have to nest statements like you're doing.

AngularJs uses $q promises:

A service that helps you run functions asynchronously, and use their return values (or exceptions) when they are done processing.

On the internet there are tons of articles about promises and how to chain them.
Recently I found this article which explains the common mistakes with promises.
It's worth reading cause it goes deep into the topic.

In AngularJs you would create a promise using the $q service:

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

This bit of code returns a promise which is resolved - since there's no async operation - when it's called. It would return an object with a property value = true.
The cool thing about promises is the fact 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.
  });

passing the result of the previous - resolved - promise.

If promises are rejected because of some exceptions:

deferred.reject({value: false});

you can trap the error and stop the execution in 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 the finally to do some cleanup 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's going to be executed at the end of the chain, even in case of error trapped by the catch.
  });

Things are not so simple, though. At the beginning you might find yourself spending a few hours debugging the code.

How would I fix your code ?

First of all I would create a function which fetch the ids calling 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've implemented the system described above. $http already returns a promise but I wrapped it creating a new promise, anyway.

Then I would have to query the database to find the non existing ids (I didn't 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 is going to be slightly different from your as I've used WebSql cause I wanted to test it in the browser.

Now we need to find the ids which do not 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 still can pipe it like you would do with a real promise (You'll find out how later).

Now we need to call the web api to fetch the posts for the ids which couldn't 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);
}

Things here get interesting.

Since I want this function to return one and only result with an array of posts I've created an array of promises. The $http service already returns a promise. I push it in an array.
At the end I try to resolve the array of promises with $q.all. Really cool!

Now we need to write the posts fetched in 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 up 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 here is quite complicated cause WebSql doesn't work with promises and I want them to be resolve in one go and get the result back.

Now what can you do with all these functions? Well, you can chain 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 find found in this gist.

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

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

How to avoid nested ActionListeners?

From Dev

How to avoid nested promises?

From Dev

How to avoid call of constructor?

From Dev

How to avoid nested ifs in C?

From Dev

How to avoid nested query in PostgreSQL

From Dev

How to avoid nested if statements in Elixir?

From Dev

How to avoid nested forEach calls?

From Dev

How to avoid deep nested callback?

From Dev

How to avoid nested if statements in Elixir?

From Dev

How to avoid nested callbacks in RxJava?

From Dev

How to avoid nested font sizes with CKEditor?

From Dev

How to avoid depth recursion in nested functions with python

From Dev

How to avoid nested for-each loops?

From Dev

How can I avoid a nested form situation?

From Dev

how to avoid of using nested lapply in R?

From Dev

How to avoid nested subscriptions with concatMap in Angular

From Dev

How to avoid nested transactions not supported error?

From Dev

How to avoid duplicates in nested foreach loop php

From Dev

How to avoid deeply nested promises in protractor

From Dev

How to avoid nested for loops in this particular case?

From Dev

How to avoid nested Single in RxJava2

From Dev

How to call the jQuery nested function

From Dev

How to call nested function in javascript?

From Dev

How to call nested Async method

From Dev

How to call nested function in javascript?

From Dev

How to avoid if statement and call multiple constructors

From Dev

How to call an Action from a helper and avoid the render?

From Dev

How to avoid if statement and call multiple constructors

From Dev

How to call nested Swift function in Xcode Debugger?