【问题标题】:AngularJS promises notify not workingAngularJS承诺通知不起作用
【发布时间】:2014-02-19 13:02:09
【问题描述】:

我有以下控制器代码:

 .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 调用似乎永远不会在控制器中执行和接收。我这里没有什么问题吗?我不确定如何让通知执行。

【问题讨论】:

  • 在我的项目中,我使用完全相同的结构来获取数据:)

标签: angularjs promise


【解决方案1】:

我试图重现您的问题here。看来,您不能直接在 promise 上调用 notify,而必须封装到 $applycall 中。

另请参阅 $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);
});

【讨论】:

    【解决方案2】:

    我设法通过在 $timeout 函数中包装通知来使其工作:

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

    看起来你不能在返回 promise 对象之前调用 notify,这有点道理。

    【讨论】:

    • 回想起来,我对承诺的了解越多似乎很明显,但这个答案为我省去了很多麻烦。
    • 那是有道理的,只是超时设置为零,所以它不只是发生在同一个地方吗?很奇怪。
    • @PixMach:回调永远不会被立即调用,你可以在这里测试它看看哪个先触发:jsfiddle.net/yL63vwpL/2。 (Angular 的 $timeout 只是一个花哨的 setTimeout)。在计时器上输入 0 将被浏览器更正为最小值。在 HTML4 中最短为 10 毫秒,在 HTML5 中为 4 毫秒。此外,您输入的时间从来都不是准确的,只是一个近似值——浏览器将尝试尽可能接近该值。也可以不带delay参数调用$timeout(function1)获取最小值。
    【解决方案3】:

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

    强制 $q .notify() 执行

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

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

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

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

    为了看到这一点,我创建了一个friendService,它通过两种不同的方法返回数据。这两种方法都尝试通过 .notify() 返回缓存数据,然后通过 .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>
    

    如您所见,控制器将相同的处理程序绑定到 promise 的“resolve”和“notify”事件。这样就可以统一处理缓存数据和直播数据。唯一的区别在于它调用的服务层方法 - get() 与 getWithNoop()。而且,如果我们调用 .get() 几次,然后调用 .getWithNoop() 几次,我们可以在控制台中看到差异。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-11-17
      • 2017-04-29
      • 2016-06-07
      • 2017-11-23
      • 2016-06-16
      • 2016-05-07
      • 2015-01-22
      • 1970-01-01
      相关资源
      最近更新 更多