【问题标题】:How to avoid nested call in ionic/angular?如何避免 ionic/angular 中的嵌套调用?
【发布时间】:2015-07-10 18:47:59
【问题描述】:

我对 ionic/angular 完全陌生,这是我的代码:

.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);
    });
  }
})

我在做什么

  1. 从服务器获取所有ID

  2. 如果 db(sqlite) 中不存在 id

  3. 通过 id 从服务器获取帖子

  4. 将帖子插入数据库

它最终嵌套得很深,很丑。

什么是离子,角度的方式来做到这一点?

【问题讨论】:

标签: angularjs ionic-framework


【解决方案1】:

正如其他人所建议的那样,最好的选择是使用 Promise,这样您就不必像现在这样嵌套语句。

AngularJs 使用 $q promises:

帮助您异步运行函数并使用它们的服务 完成处理后返回值(或异常)。

互联网上有大量关于 Promise 以及如何链接它们的文章。
最近我发现了这个article,它解释了 promise 的常见错误。
值得一读,因为它深入主题。

在 AngularJs 中,您将使用 $q 服务创建一个承诺:

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

这段代码返回一个已解决的承诺 - 因为没有异步操作 - 当它被调用时。它将返回一个具有属性value = true 的对象。
Promise 最酷的地方在于您可以将它们链接起来:

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.
  });

传递上一个 - 已解决 - 承诺的结果。

如果 promise 因为某些异常而被拒绝:

deferred.reject({value: false});

您可以捕获错误并停止链中的执行:

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做一些清理或其他事情:

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.
  });

不过,事情并不是那么简单。一开始你可能会发现自己花了几个小时调试代码。

我将如何修复您的代码?

首先,我将创建一个函数来获取调用 web api 的 id:

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;
}

如您所见,我已经实现了上述系统。 $http 已经返回了一个 Promise,但我还是将它包装成一个新的 Promise。

然后我将不得不查询数据库以找到不存在的 ID(我没有将我的代码放在循环中,因为在一次调用中获取所有记录更容易):

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;
}

我的代码与您的代码略有不同,因为我使用了 WebSql,因为我想在浏览器中对其进行测试。

现在我们需要找到 db 中不存在的 id:

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;
}

这个函数不会返回一个 Promise,但你仍然可以像处理一个真正的 Promise 一样通过管道传递它(稍后你会知道如何)。

现在我们需要调用 web api 来获取在数据库中找不到的 id 的帖子:

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);
}

这里的事情变得有趣了。

由于我希望此函数返回一个且仅返回一组帖子的结果,因此我创建了一组承诺。 $http 服务已经返回了一个承诺。我将它推入一个数组中。
最后,我尝试使用$q.all 解决一系列承诺。真的很酷!

现在我们需要编写从数据库中获取的帖子。

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);
}

再一次,我们链接了一系列 Promise,以便我们可以一次性解决所有问题。 这个函数在这里调用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);
                });
        });
    });
}

这里的这一点非常复杂,因为 WebSql 不适用于 Promise,我希望它们一次性解决并取回结果。

现在您可以使用所有这些功能做什么?好吧,你可以像我之前解释的那样链接它们:

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.
    });

最终结果可以在这个gist中找到。

如果您希望下载整个应用程序并在您的 PC 上进行测试,这就是 link (myApp)。

【讨论】:

  • 很高兴我能帮上忙。我已经在你可以在答案底部找到的要点中重新创建了整个问题,以防你想尝试调试它。总是欢迎投票:-)
  • 是的,根据您的精彩回答,我已经尝试并成功解决了我的问题,我学到了很多东西,也感谢您提供的链接。
  • @Sato:我已经在 OneDrive 上上传了整个源代码,以备不时之需。干杯。
  • 非常感谢,你真好:)
猜你喜欢
  • 2019-05-18
  • 2020-03-25
  • 2018-10-15
  • 1970-01-01
  • 2022-08-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多