AngularJS承诺通知不工作

时间:2014-01-28 09:48:13

标签: angularjs promise

我有以下控制器代码:

 .controller('Controller1', function ($scope, MyService) {

    var promise = MyService.getData();
    promise.then(function(success) {
        console.log("success");
    }, function(error) {
        console.log("error");
    }, function(update) {
        console.log("got an update!");
    }) ;

}

在我的services.js中:

 .factory('MyService', function ($resource, API_END_POINT, localStorageService, $q) {
   return {
       getData: function() {
           var resource = $resource(API_END_POINT + '/data', {
               query: { method: 'GET', isArray: true }
           });

           var deferred = $q.defer();
           var response = localStorageService.get("data");
           console.log("from local storage: "+JSON.stringify(response));
           deferred.notify(response);

           resource.query(function (success) {
               console.log("success querying RESTful resource")
               localStorageService.add("data", success);
               deferred.resolve(success);
           }, function(error) {
               console.log("error occurred");
               deferred.reject(response);
           });

           return deferred.promise;
       }
   }

})

但由于某种原因,deferred.notify调用似乎永远不会执行并在控制器中被接收。我这里有什么不对吗?我不知道如何让通知执行。

3 个答案:

答案 0 :(得分:21)

我设法通过在$ timeout函数中包装notify来实现它:

$timeout(function() {
  deferred.notify('In progress')}
, 0)

看起来你无法在返回promise对象之前调用notify,这有点意义。

答案 1 :(得分:2)

来源:http://www.bennadel.com/blog/2800-forcing-q-notify-to-execute-with-a-no-op-in-angularjs.htm

强制$ q .notify()执行

.notify()事件的美妙之处在于,我们的数据访问层可以使用它来提供即时可用但陈旧的"数据,同时仍然使用.resolve()事件,除了实时数据。这为调用上下文 - 您的控制器 - 提供了很好的洞察力和对缓存数据集的控制,以及它[控制器]是否甚至想要合并缓存数据。

但是,我们遇到了一些竞争条件。拥有缓存数据的数据访问服务需要在将promise返回给调用上下文之前调用.notify()。这意味着您的控制器在调用.notify()之后绑定到notify事件。从哲学的角度来看,这应该没问题 - Promises(以及几乎所有事件驱动的)都旨在异步调用绑定,以便创建访问的一致性。

然而,从实际的角度来看,它并不那么简单。虽然AngularJS遵循这一理念,但它也增加了一些优化来减少处理。在我们的具体情况下,AngularJS不会在延迟对象中安排回调处理,除非它发现至少有一个回调被绑定(否则它认为世界不是在监听)。因此,我们的控制器永远不会收到有关缓存数据的通知。

为了解决这个问题,我们可以让服务层在调用.notify()之前将无操作(无操作)函数绑定到notify事件。这样,当它调用.notify()时,AngularJS将看到至少有一个回调被注册,并且它将在下一个tick中安排刷新待处理队列(通过$ rootScope。$ evalAsync()实现)。这允许我们的控制器获得缓存数据的通知,即使它在调用.notify()之后绑定到notify事件。

为了看到这一点,我创建了一个通过两种不同方法返回数据的friendService。这两种方法都试图通过.notify()然后" live"返回缓存数据。数据通过.resolve()。这两种方法的唯一区别是在调用.notify()之前将no-op绑定到notify事件。

<!doctype html>
<html ng-app="Demo">
<head>
<meta charset="utf-8" />

<title>
    Forcing $q .notify() To Execute With A No-Op In AngularJS
</title>

<link rel="stylesheet" type="text/css" href="./demo.css"></link>
</head>
<body ng-controller="AppController">

<h1>
    Forcing $q .notify() To Execute With A No-Op In AngularJS
</h1>

<h2>
    Friends
</h2>

<div ng-switch="isLoading">

    <!-- Show while friends are being loaded. -->
    <p ng-switch-when="true">
        <em>Loading...</em>
    </p>

    <!-- Show once the friends have loaded and are available in the view-model. -->
    <ul ng-switch-when="false">
        <li ng-repeat="friend in friends track by friend.id">
            {{ friend.name }}
        </li>
    </ul>

</div>

<p>
    <a ng-click="load()">Load</a>
    &nbsp;|&nbsp;
    <a ng-click="loadWithNoop()">Load With No-Op</a>
</p>


<!-- Load scripts. -->
<script type="text/javascript" src="../../vendor/angularjs/angular-1.3.13.min.js"></script>
<script type="text/javascript">

    // Create an application module for our demo.
    var app = angular.module( "Demo", [] );


    // -------------------------------------------------- //
    // -------------------------------------------------- //


    // I control the root of the application.
    app.controller(
        "AppController",
        function( $scope, friendService ) {

            $scope.isLoading = false;

            $scope.friends = [];

            // Load the friend data (defaults to "get" method vs. "getWithNoop").
            loadRemoteData();


            // ---
            // PUBLIC METHODS.
            // ---


            // I reload the list of friends using friendService.get().
            $scope.load = function() {

                loadRemoteData( "get" );

            };


            // I reload the list of friends using friendService.getWithNoop().
            $scope.loadWithNoop = function() {

                loadRemoteData( "getWithNoop" );

            };


            // ---
            // PRIVATE METHODS.
            // ---


            // I load the friends from the friend repository. I am passing-in the
            // method name to demonstrate that, from the Controller's point-of-view,
            // nothing here is different other than the name of the method. The real
            // substantive difference exists in the implementation of the friend-
            // Service method and how it interacts with $q / Deferred.
            function loadRemoteData( loadingMethod ) {

                console.info( "Loading friends with [", loadingMethod, "]" );

                // Indicate that we are in the loading phase.
                $scope.isLoading = true;

                // When we make the request, we expect the service to try to use
                // cached-data, which it will make available via the "notify" event
                // handler on the promise. As such, we're going to wire up the same
                // event handler to both the "resolve" and the "notify" callbacks.
                friendService[ loadingMethod || "get" ]
                    .call( friendService )
                    .then(
                        handleResolve, // Resolve.
                        null,
                        handleResolve // Notify.
                    )
                ;

                function handleResolve( friends ) {

                    // Indicate that the data is no longer being loaded.
                    $scope.isLoading = false;

                    $scope.friends = friends;

                    console.log( "Friends loaded successfully at", ( new Date() ).getTime() );

                }

            }

        }
    );


    // -------------------------------------------------- //
    // -------------------------------------------------- //


    // I provide access to the friend repository.
    app.factory(
        "friendService",
        function( $q, $timeout ) {

            // Our friend "repository".
            var friends = [
                {
                    id: 1,
                    name: "Tricia"
                },
                {
                    id: 2,
                    name: "Heather"
                },
                {
                    id: 3,
                    name: "Kim"
                }
            ];

            // Return the public API.
            return({
                get: get,
                getWithNoop: getWithNoop
            });


            // ---
            // PUBLIC METHODS.
            // ---


            // I return the list of friends. If the friends are cached locally, the
            // cached collection will be exposed via the promise' .notify() event.
            function get() {

                var deferred = $q.defer();

                // Notify the calling context with the cached data.
                deferred.notify( angular.copy( friends ) );

                $timeout(
                    function networkLatency() {

                        deferred.resolve( angular.copy( friends ) );

                    },
                    1000,
                    false // No need to trigger digest - $q will do that already.
                );

                return( deferred.promise );

            }


            // I return the list of friends. If the friends are cached locally, the
            // cached collection will be exposed via the promise' .notify() event.
            function getWithNoop() {

                var deferred = $q.defer();

                // -- BEGIN: Hack. ----------------------------------------------- //
                // CAUTION: This is a work-around for an optimization in the way
                // AngularJS implemented $q. When we go to invoke .notify(),
                // AngularJS will ignore the event if there are no pending callbacks
                // for the event. Since our calling context can't bind to .notify()
                // until after we invoke .notify() here (and return the promise),
                // AngularJS will ignore it. However, if we bind a No-Op (no
                // operation) function to the .notify() event, AngularJS will
                // schedule a flushing of the deferred queue in the "next tick,"
                // which will give the calling context time to bind to .notify().
                deferred.promise.then( null, null, angular.noop );
                // -- END: Hack. ------------------------------------------------- //

                // Notify the calling context with the cached data.
                deferred.notify( angular.copy( friends ) );

                $timeout(
                    function networkLatency() {

                        deferred.resolve( angular.copy( friends ) );

                    },
                    1000,
                    false // No need to trigger digest - $q will do that already.
                );

                return( deferred.promise );

            }

        }
    );

</script>

</body>
</html>

正如您所看到的,控制器将相同的处理程序绑定到&#34;解决&#34;和&#34;通知&#34;承诺的事件。通过这种方式,它可以统一处理缓存的数据和实时数据。唯一的区别在于它调用的服务层方法 - get()与getWithNoop()。而且,如果我们几次调用.get()然后几次调用.getWithNoop(),我们可以在控制台中看到差异。

答案 2 :(得分:1)

我尝试重现您的问题here。看来,您无法直接在承诺上调用notify,但必须进行$apply调用。

另请参阅$q here的文档。

引用示例中的确切行:

  

因为这个fn在事件循环的未来转向中执行异步,所以我们需要将代码包装到$ apply调用中,以便正确地观察模型更改。

你可以试试这个并改变你的代码:

deferred.notify(response); // should not work

resource.query(function (success) {
    deferred.notify('Returning from resource'); // should work
    console.log("success querying RESTful resource")
    localStorageService.add("data", success);
    deferred.resolve(success);
}, function(error) {
    deferred.notify('caught error!'); //should also work
    console.log("error occurred");
    deferred.reject(response);
});