简体   繁体   中英

AngularJS websockets too fast

I am using AngularJS to power my webapp. Recently I've decided to introduce Stomp over websockets in the mix with RabbitMQ. One problem that I keep hitting is that sometimes the messages sent from the backend are at so high frequency that Angular fails to trigger some events losing data in the process.

The application is rather complex so sometimes a message might pass through the following chain

  1. StompJS Angular service wrapper. Uses $rootScope.$broadcast to notify components when new messages have arrived
  2. Controller that registers to messages that are being broadcast on $rootScope. Controller then updates bindings or sends another event to a component to perform some data updates.

As I mentioned sometimes the scopes don't get updated properly, even though the events are being sent and the values are being populated the view does not show the updated values. I have used $scope.$apply, $timeout, $scope.$digest and nothing seems to work... For example if I have 2 packets coming one after another and there's little or no delay between them on the socket nothing happens but if there's one the same piece of code functions properly. How to overcome this ?

To further exemplify after some questions posted by the users:

I will take the most simple example: i get progress reports from a job that runs on the backend. Basically it tells me how many rows have been written in a Cassandra database. So I get notifications like {wrote: 8000, total: 4000000}. "wrote" is being increased as the workers write in the database and since there are multiple workers sometimes these notifications get pushed very fast. I have a custom grid component written that listens for events like grid:updateCell (which allows me to update a cell with the progress report). Every time a packet comes on the socket I broadcast that event ($scope.$broadcast because the grid it is a child of page the controller). I have noticed that not all the updates from the socket get reflected in the UI although the event is caught and the grid event is also triggered with successful update of the data model but the not the UI

It seems to me you might have a few things going on here.

1) Using $scope.$broadcast bubbles down the scope which could be quite heavy if you have lots of nested scopes. I prefer to attach event listeners to the $rootScope and use $emit which only broadcast to the current scope and is quicker. I have the below service for this

    /**
     * @ngdoc service
     * @name someModule.eventbus
     * @requires $rootScope
     *
     * @description
     * Provides a eventing mechanism when a user cna broadcast and subscribe to application wide events.
     */
    angular.module('someModule').factory('eventbus', [
        '$rootScope',
        function ($rootScope) {
            /**
             * @ngdoc function
             * @name subscribe
             * @methodOf someModule.eventbus
             *
             * @description
             * Subscribes a callback to the given application wide event
             *
             * @param {String} eventName The name of the event to subscribe to.
             * @param {Function} callback A callback which is fire when the event is raised.
             * @return {Function} A function tht can be called to unsubscrive to the event.
             */
            var subscribe = function (eventName, callback) {
                    return $rootScope.$on(eventName, callback);
                },

                /**
                 * @ngdoc function
                 * @name broadcast
                 * @methodOf someModule.eventbus
                 *
                 * @description
                 * Broadcasts the given event and data.
                 *
                 * @param {String} eventName The name of the event to broadcast.
                 * @param {object} data A data object that will be passed along with the event.
                 */
                broadcast = function (eventName, data) {
                    $rootScope.$emit(eventName, data);
                };

            return {
                subscribe: subscribe,
                broadcast: broadcast
            };
        }
    ]);
  1. You might not be firing the update within an Angular digest cycle which would explain why you are only getting some updates. Try doing the update with a digest cycle (put the function within a $timeout block which is the recommended way over $scope.$apply

    $timeout(function () { doUpdate(dataFromMessageBus); });

  2. If you get alot of messages you should use a debounce function see lodash-debouce . A person can only really process in circa 200ms chunks so generally updating every 200ms will be the most someone can take in. I would personally do it every second or so in say a trading app etc.

(using lodash - debounce)

function (messageBus) {
  var debounceFn = _.debounce(function () {
      $timeout(function() {
        doUpdate(data);
      });
    }, 1000);

    // just assuming here about your socket / message bus stuff
    messageBus.onMessageRecieved(function () {
      //Call debounce function which will only actually call the inner func every 1000ms
      debounceFn();
   });

}

I had the same issue before and I was able to solve it by wrapping the websocket incoming message handler which updates my Angular Grid in the following:

$scope.$apply(function () {

//your code to update the Angular UI

});

Example:

    /**
     * @ngdoc function
     * @description
     * On Connect. Callback to subscribe to specific socket destination. 
     */
    function _subscribeBcQueue(){
        var uuid = SessionService.getUuid();
        var userBcQueue = CONFIG.SOCKET.bcQueue+'/'+uuid
        var subscription = $stomp.subscribe(userBcQueue, function (payload, headers, message) {
            _onBcMessageHandler(message)
        }, {})
    }

    /**
     * @ngdoc function
     * @description
     * OnBcMessageHandler. Callback function to handle the incoming direct message. 
     */
    function _onBcMessageHandler(message){
        //TODO Process the broadcasting status message
        var bcMsg = JSON.parse(message.body);
        console.log("Broadcasting status message:" + bcMsg);
        _updateBcJobGridRow(bcMsg)
    }



    function _updateBcJobGridRow(job) {
            $scope.$apply(function () {
                //Get the old JobGridRow 
                console.log("jobId: " + JSON.stringify(job.id));
                var oldJobGridRow = $filter('filter')(vm.gridOptions.data, {id: job.id})[0];
                console.log("oldJobGridRow: " + JSON.stringify(oldJobGridRow));
                if (oldJobGridRow) {
                    var newJobGridRow = _getBcJobGridRow(job);
                    console.log("newJobGridRow: " + JSON.stringify(newJobGridRow));
                    oldJobGridRow.progress = newJobGridRow.progress;
                    oldJobGridRow.percentage = newJobGridRow.percentage;
                    oldJobGridRow.status = newJobGridRow.status;
                    oldJobGridRow.total = newJobGridRow.total;
                    oldJobGridRow.sent = newJobGridRow.sent;
                    oldJobGridRow.recipients = newJobGridRow.recipients;
                }
            });
        }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM