AngularJS承诺通知不起作用

kadbb459  于 2022-10-24  发布在  Angular
关注(0)|答案(3)|浏览(179)

我有以下控制器代码:

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

}
在My 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调用似乎从未在控制器内执行和接收。我是不是有什么不对劲?我不确定如何让通知执行。

kyvafyod

kyvafyod1#

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

强制执行$Q.Notify()

Notify()事件的美妙之处在于,我们的数据访问层可以使用它来提供“立即可用的、但过时的”数据,同时仍然使用.Resolve()事件来处理实时数据。这使调用上下文(您的控制器)能够很好地洞察和控制缓存的数据集,以及它(控制器)是否甚至想要合并缓存的数据。
但是,我们遇到了一点种族问题。拥有缓存数据的数据访问服务在将承诺返回到调用上下文之前需要调用.tify()。这意味着您的控制器在调用.tify()之后绑定到Notify事件。从哲学的Angular 来看,这应该很好-承诺(以及几乎所有事件驱动的内容)旨在异步调用绑定,以便创建统一的访问。
然而,从实际的Angular 来看,事情并不是那么简单。虽然AngularJS遵循这一理念,但它也添加了一些优化以减少处理。具体地说,在我们的例子中,AngularJS不会在延迟对象中调度回调处理,除非它发现至少有一个回调被绑定(否则它认为世界没有在监听)。因此,我们的控制器永远不会收到有关缓存数据的通知。
为了解决这个问题,我们可以让服务层在Notify事件调用.fy()之前将一个no-op(无操作)函数绑定到Notify事件。这样,当AngularJS确实调用.tify()时,它将看到至少注册了一个回调,并且它将在下一个计时(通过$rootScope.$valAsync()实现)调度挂起队列的刷新。这允许我们的控制器得到关于缓存数据的通知,即使它在调用.fy()之后绑定到Notify事件。
为了实际了解这一点,我创建了一个FriendService,它通过两种不同的方法返回数据。这两种方法都尝试通过.tify()返回缓存数据,然后通过.Resolve()返回“实时”数据。这两种方法之间的唯一区别是,一种方法在调用.fy()之前将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>

如您所见,控制器将相同的处理程序绑定到承诺的“Resolve”和“Notify”事件。通过这种方式,可以统一处理缓存数据和实时数据。唯一的区别是它调用哪个服务层方法-get()和getWithNoop()。而且,如果我们调用几次.get(),然后调用几次.getWithNoop(),我们可以在控制台中看到区别。

lpwwtiir

lpwwtiir2#

我试着重现你的问题。看起来,您不能直接对Promise调用notify,而必须 Package 到$apply调用中。
另请参阅此处的$q文档。
引用示例中的准确行:
由于此fn在事件循环的未来回合中执行异步,因此我们需要将代码 Package 到$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);
});
k10s72fa

k10s72fa3#

我设法将Notify Package 在$Timeout函数中,从而使其正常工作:

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

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

相关问题