【问题标题】:Nested promises not resolving until a button is clicked在单击按钮之前,嵌套的承诺不会解决
【发布时间】:2013-09-01 11:32:36
【问题描述】:

我正在构建一个 AngularJS 应用程序(使用 1.2.0 RC1),它使用 REST API 来保存用户输入的数据。用户将输入有关公司的数据,它包含我需要保存的两个核心组件:

  • 公司详情
  • 该公司内的联系人

我有 1 个 REST 方法可以调用来保存公司,然后我有另一个方法可以调用来保存每个联系人。

我计划使用$q 跟踪所有请求并在完成后解决它们。

我遇到的问题是,虽然 REST API 被成功调用并且我的所有数据都被保存,但当所有承诺都解决时,我的控制器没有收到通知直到我单击屏幕上的按钮,例如add contact 按钮。我已经尝试消除 REST API 作为问题的根源,但在使用 setTimeout 模拟延迟作业时仍然会发生这种情况。

这是我的service 模块:

angular.module('myApp', ['restService'])
    .factory('service', ['client', '$q', function (client, $q) {
        return {
            create: function (name, people) {
                var deferred = $q.defer();

                setTimeout(function () {
                    console.log('saved company ' + name);

                    var promises = people.map(function (person) {
                        var dfd = $q.defer();

                        setTimeout(function () {
                            console.dir(person);
                            console.log('Person saved');
                            dfd.resolve();
                        }, 100);

                        return dfd.promise;
                    });

                    $q.all(promises).then(deferred.resolve);
                }, 100);

                return deferred.promise;
            }
        };
    }]);

这是我的控制器:

angular.module('club', ['myApp'])
    .controller('RegisterCtrl', ['$scope', 'service', function ($scope, service) {
        $scope.addPerson = function () {
            $scope.company.people.push({
                firstName: '',
                lastName: '',
                email: ''
            });
        };

        $scope.removePerson = function (person, index) {
            $scope.company.people.splice(index, 1);
        };

        $scope.save = function (company) {
            service.createClub(company.name, company.people).then(function () {
                console.log('And we\'re done');
            });
        };

        $scope.company = {
            name: '',
            people: []
        };
    }]);

所以当我调用save 方法(即绑定到按钮的ng-click)时,我将设置一个console.log,如下所示:

saved company
Object
Person saved
Object
Person saved

请注意,没有And we're done 输出。只要我点击addPerson 绑定到ng-click 的按钮,我就会得到最终的消息输出。

【问题讨论】:

    标签: angularjs promise


    【解决方案1】:

    Angular 不会神奇地监视所有超时。它需要一个钩子来告知某些事情可能发生了变化,这会触发$digest 循环。

    您可以通过调用$scope.$apply() 或使用内置处理程序(如ng-click)手动执行此操作。但是对于这个用例,您还可以使用 $timeout 服务来代替 setTimeout

    API Reference/ng/$timeout

    【讨论】:

    • 你会在哪里打电话给$scope.$apply?我正在使用ng-clickcontroller.save 绑定到UI 上的button,这就是触发整个过程的方法。
    • 正如 Glen 所说,$timeout 是您的最佳选择。在您的服务中注入 $timeout 并将 setTimeout(...) 替换为 $timeout(...)
    【解决方案2】:

    强烈建议使用 AngularJS $http 服务与您的 REST API 进行通信,因为它会为您更新模型。

    它返回一个承诺,您可以将 successerror 处理程序链接到,因此它非常通用。

    翻译成你的代码,你可以有这样的服务:

    angular.module('myApp', [])
        .factory('person', ['$http', function ($http) {
            return {
                create: function (data) {
    
                    // Return promise
                    return $http.post('/yourApiUrl', data);
                }
            };
        }]);
    

    还有这样的控制器:

    angular.module('myApp', [])
        .controller('yourController', ['person', function (person) {
    
            $scope.company = {
                name: '',
                people: []
            };
    
            $scope.addPerson = function(data){
    
                //Save person to API
                person.create(data)
    
                    // Success handler
                    .success(function(data, status, headers, config) {
    
                        // Parse person from response (e.g. if your API 
                        // returns the created object after a POST)
                        var person = data;
    
                        // Add person to company
                        $scope.company.people.push(person)
                    })
    
                    // Error handler
                    .error(function(data, status, headers, config) {
    
                        // Handle error
                    });
    
            };
    
        }]);
    

    通过从服务返回承诺,您可以在控制器中自定义 successerror 处理程序,从而实现非常灵活的解决方案。

    而且您不必创建自己的承诺。 AngularJS 将为您处理这一切。

    希望对你有帮助!

    【讨论】:

    • 我使用 Azure 移动服务作为 REST API,它提供了一个我不想重写的 JS 客户端 API,所以我不能使用 $http
    • 使用 3rd 方脚本时,您可以随时调用 $scope.apply() 来让 AngularJS 更新绑定。
    • 但是你在哪里打电话给$scope.$apply()?我已经尝试在我的控制器的$scope.save 方法中执行此操作,但由于它已经在$apply 中(因为save 是由ng-click 绑定触发的),所以出错了。我也把它放在$q.all 周围,但这也没有解决它
    • 这取决于你的代码在做什么。如果您的第三方代码执行了您需要在 AngularJS 世界中反映的更改,您需要执行$scope.$apply()。通过查看您的示例代码,保存处理程序中指定的回调(您的日志“我们完成了”)应该有一个$scope.$apply()。如果您使用您的代码创建一个 plnkr,我将非常乐意看看...
    【解决方案3】:

    您总是希望在作用域上下文中调用 deferred.resolve() 和 deferred.reject()。在您的情况下,您正在从角度框架之外调用代码(setTimeout,并且实际使用您在评论中提到的用于 Azure 移动服务的 JS 客户端 API),因此您应该将 dfd.resolve() 包装在 $rootScope 中。 $apply() (不要忘记将 $rootScope 注入到您的服务中)。

    http://plnkr.co/edit/3sCHEjrtMdqCvFQ7ILTD

    这个 Plunkr 展示了一个工作示例。如果您将 $rootScope.$apply(dfd.resolve()) 更改回 dfd.resolve(),它就会出现您在问题中描述的问题。

    请注意,由于本文中列出的原因,使用 $rootScope.$apply(function(){dfd.resolve()}) 可能也更好:

    http://jimhoskins.com/2012/12/17/angularjs-and-apply.html

    【讨论】:

      猜你喜欢
      • 2017-04-02
      • 2019-05-29
      • 2015-06-24
      • 1970-01-01
      • 1970-01-01
      • 2017-04-04
      • 2015-06-23
      • 2020-04-18
      • 1970-01-01
      相关资源
      最近更新 更多