将承诺传递给Angular-UI状态控制器

时间:2014-05-06 10:37:00

标签: javascript angularjs promise angular-ui angular-ui-router

是否可以从外部控制器(例如触发状态的控制器)将承诺传递给UI.Router $state

我知道$state.go()会回复一个承诺;是否可以覆盖您自己的承诺直接自己解决此承诺或使用新承诺解决它?

此外,文档说$state.go()返回的承诺可以被另一个承诺拒绝(由transition superseded表示),但我无法找到指示如何通过在州内。

例如,在下面的代码中,我希望能够等到用户点击按钮($scope.buttonClicked()),然后再继续doSomethingElse()

我知道我可以发布一个事件,但由于承诺被深深地融入Angular,我想知道是否有办法通过promise.resolve / promise.reject来实现这一目标。

angular.module('APP', ['ui.router'])
.config(['$stateProvider', function ($stateProvider) {
    $stateProvider
    .state('myState', {
        template: '<p>myState</p>',
        controller: ['$state', '$scope', '$q', function ($state, $scope, $q) {
            var deferred = $q.defer();

            $scope.buttonClicked = function () {
                deferred.resolve();
            }
        }]
    });
}])
.controller('mainCtrl', ['$state', function ($state) {
    $state.go('myState')
    .then(doSomethingElse)
}]);

更新 我接受了@ blint的回答,因为它让我最接近我想要的。下面是一些代码,可以更好地充实这个答案的想法。我不认为我写的方式是一个非常优雅的解决方案,如果有人可以提出更好的方法来解决触发状态下的承诺,我感到很高兴。

我选择的解决方案是像你通常在你的控制器中那样链接你的承诺,但是留下一个附加到该范围的$scope.next()方法(或类似的东西)来解决/拒绝承诺。由于状态可以继承调用控制器的作用域,因此它可以直接调用该方法,从而解析/拒绝该promise。以下是它的工作原理:

首先,使用调用$scope.next()方法的按钮/控制器设置状态:

.config(function ($stateProvider) {
    $stateProvider
    .state('selectLanguage', {
        template: '<p>Select language for app: \
            <select ng-model="user.language" ng-options="language.label for language in languages">\
                <option value="">Please select</option>\
            </select>\
            <button ng-click="next()">Next</button>\
            </p>',
        controller: function ($scope) {
            $scope.languages = [
                {label: 'Deutch', value: 'de'},
                {label: 'English', value: 'en'},
                {label: 'Français', value: 'fr'},
                {label: 'Error', value: null}
            ];
        }
    })
    .state('getUserInfo', {
        template: '<p>Name: <input ng-model="user.name" /><br />\
            Email: <input ng-model="user.email" /><br />\
            <button ng-click="next()">Next</button>\
            </p>'
    })
    .state('mainMenu', {
        template: '<p>The main menu for {{user.name}} is in {{user.language.label}}</p>'
    })
    .state('error', {
        template: '<p>There was an error</p>'
    });
})

接下来,设置控制器。在这种情况下,我使用本地服务方法user.loadFromLocalStorage()来推动滚动(它返回一个承诺),但任何承诺都会这样做。在此工作流程中,如果$scope.user缺少任何内容,它将逐步使用状态填充。如果已完全填充,则会跳到主菜单。如果元素保留为空或处于无效状态,则会进入错误视图。

.controller('mainCtrl', function ($scope, $state, $q, User) {
    $scope.user = new User();

    $scope.user.loadFromLocalStorage()
    .then(function () {
        var deferred;

        if ($scope.user.language === null) {
             deferred = $q.defer();

             $state.go('selectLanguage');

             $scope.next = function () {
                $scope.next = undefined;

                if ($scope.user.language === null) {
                    return deferred.reject('Language not selected somehow');
                }

                deferred.resolve();
             };

             return deferred.promise;
        }
    })
    .then(function () {
        var deferred;

        if ($scope.user.name === null || $scope.user.email === null) {
            deferred = $q.defer();

            $state.go('getUserInfo');
            $scope.next = function () {
                $scope.next = undefined;

                if ($scope.user.name === null || $scope.user.email === null) {
                    return deferred.reject('Could not get user name or email');
                }

                deferred.resolve();
            };

            return deferred.promise;
        }


    })
    .then(function () {
        $state.go('mainMenu');
    })
    .catch(function (err) {
        $state.go('error', err);
    });

});

这非常冗长,但还不是很干,但它显示了使用promises进行异步流控制的整体意图。

2 个答案:

答案 0 :(得分:3)

promises的目的是保证结果......或处理失败。承诺可以链接,在函数中返回,从而扩展。

你没有兴趣“覆盖”一个承诺。但是你能做什么:

  • 处理失败案例。以下是文档中的示例:
promiseB = promiseA.then(function(result) {
    // success: do something and resolve promiseB
    //          with the old or a new result
    return result;
  }, function(reason) {
    // error: handle the error if possible and
    //        resolve promiseB with newPromiseOrValue,
    //        otherwise forward the rejection to promiseB
    if (canHandle(reason)) {
     // handle the error and recover
     return newPromiseOrValue;
    }
    return $q.reject(reason);
  });
  • 在promise链中附加一个新的异步操作。你可以结合承诺。如果链中调用的方法返回一个promise,那么一旦新的promise得到解决,承诺的父方将覆盖链的其余部分。

以下是您可能正在寻找的模式:

angular.module('APP', ['ui.router'])
.config(['$stateProvider', function ($stateProvider) {
    $stateProvider
    .state('myState', {
        template: '<p>myState</p>',
        controller: 'myCtrl'
    });
}])
.controller('myCtrl', ['$scope', '$state', '$q', '$http', 'someAsyncServiceWithCallback',
    function ($scope, $state, $q, $http, myService) {
    $scope.buttonClicked = function () {
        $state.go('myState')
        .then(function () {
            // You can return a promise...
            // From a method that returns a promise
            // return $http.get('/myURL');

            // Or from an old-school method taking a callback:
            var deferred = $q.defer();
            myService(function(data) {
                deferred.resolve(data);
            });

            return deferred.promise;
        },
        function () {
            console.log("$state.go() failed :(");
        });
    };
}]);

答案 1 :(得分:1)

也许实现这一目标的一种方法是从州resolve

返回你的承诺
resolve: {
    myResolve: function($scope, $q) {
        var deferred = $q.defer();
        $scope.buttonClicked = function () {
           deferred.resolve();
        }
        return deferred.promise;
    }
}

resolve docs中还有一个可能感兴趣的例子

 // Another promise example. If you need to do some 
 // processing of the result, use .then, and your 
 // promise is chained in for free. This is another
 // typical use case of resolve.
 promiseObj2:  function($http){
    return $http({method: 'GET', url: '/someUrl'})
       .then (function (data) {
           return doSomeStuffFirst(data);
       });
 },