AngularJS Promises $q.all and SignalR

2019-07-09 03:23发布

问题:

I have checked number of answers regarding promises, but I can't get my code working (perhaps I'm doing something terribly wrong in other place)

In general I'm working on small test application using AngularJS and SignalR. I have signalR service which looks like this:

(function () {
'use strict';

var serviceId = 'signalRSvc';
angular.module('app').service(serviceId, ['$rootScope', signalrcontext]);

function signalrcontext( $rootScope) {
    var performanceHub = null;
    var connection = null;        

    var service = {
        initialize: initialize,
        getPerformanceCounters: getPerformanceCounters,
        getAllValues: getAllValues
    };

    return service;

    function initialize() {
        connection = $.connection;

        performanceHub = connection.webServicePerformanceHub;
        connection.hub.logging = true;

        performanceHub.client.updatePerformanceData = function(performanceData) {
            $rootScope.$emit("updatePerformanceData", performanceData);
        };

        return connection.hub.start();
    };

    function getPerformanceCounters() {
        return performanceHub.server.getPerformanceCounters();
    };

    function getAllValues(id) {
        return performanceHub.server.getAllValues(id);
    };
}
})();

What i'm trying to do is to initilize SignalR, then execute method getPerformanceCounters() which will download list of counters to render (returned as array of objects), then for each counter I would like to get the data, and here problem starts. According to MS documents, SignalR proxy methods are returning promises. I have wrote this code, but i have no idea why it is not working on step getAllValues (according to this answer AngularJS Promises, $q, defer it should work)

   function initSignalR() {
        return signalRSvc.initialize().then(function() {                
            return signalRSvc.getPerformanceCounters();
        }).then(function(configurations) {
            log('performance counters configuration downloaded');
            return getAllValues(configurations);
        }).then(function (resultData) {
            vm.resultData = resultData;
        });
    }

    function getAllValues(configurations) {
        var promises = new Array();

        angular.forEach(configurations, function(configuration) {
            promises.push(signalRSvc.getAllValues(configuration.Id));
        });

        return $q.all(promises);
    }

In my understanding, last then should execute when all 'calls' to signalRSvc.getAllValues are completed and resultData should contain array of objects returned from those promises. Instead of this, I'm getting some garbage which is not even an array.

Surprisingly (for me of course) when I do something like this

    function getAllValues(configurations) {
        var promises = new Array();

        angular.forEach(configurations, function(configuration) {
            promises.push(signalRSvc.getAllValues(configuration.Id));
        });

        return $q.all(promises).then(function(resultData) {  
           //here result data is fine!!
        });
    }

in nested then result data is fine (but of course the order of resolving promises is messed up).

Thanks for help in advance since i'm out of ideas.

回答1:

My friend Yan Yankowski wrote a great SignalR wrapper for AngularJS. you can download it from GitHub, this wrapper also uses $q promises for getting the results.

Example:

hubFactory.getHub("myHub").run("myMethod", param_1, param_2, .... param_n).then ( function(responseData) {} )


回答2:

It looks like the problem was related to the promises returned by SingalR (my colleague from my team found this) . SignalR is returning jQuery promises which are not "compatible" in all cases with AngularJS promises. Solution was to wrap methods from signalR proxy with $q.when. Now everything is working fine.

Fixed code (which still contains some other SignalR related issues)

.....

return service;

function initialize() {
    connection = $.connection;

    performanceHub = connection.webServicePerformanceHub;
    connection.hub.logging = true;

    performanceHub.client.updatePerformanceData = function(performanceData) {
        $rootScope.$emit("updatePerformanceData", performanceData);
    };

    return $q.when(connection.hub.start();
};

function getPerformanceCounters() {
    return $q.when(performanceHub.server.getPerformanceCounters());
};

function getAllValues(id) {
    return $q.when(performanceHub.server.getAllValues(id));
};
.....

Promise chaining which was not working previously is now working fine

function initSignalR() {
    return signalRSvc.initialize().then(function () {
        return signalRSvc.getPerformanceCounters();
    }).then(function (configurations) {
        chartConfigurations = configurations;                
        return getAllValues(configurations);
    }).then(function (chartData) {
        angular.forEach(chartData, function(value, key) {
        chartConfigurations[key].chartData = convertToChartDataset(value);
        chartConfigurations[key].options = {
            animation: false
            };
        });

        vm.configurations = chartConfigurations;
    });
}

function getAllValues(configurations) {
    var promises = [];

    $.each(configurations, function (index, value) {
        promises.push(signalRSvc.getAllValues(value.Id));
    })

    return $q.all(promises);
}


回答3:

Can you just change initSignalR() to the following?

function initSignalR() {
    return signalRSvc.initialize().then(function() {                
        return signalRSvc.getPerformanceCounters();
    }).then(function(configurations) {
        log('performance counters configuration downloaded');
        getAllValues(configurations).then(function (resultData) {
            vm.resultData = resultData;
        });
    });
}

I know it's not pretty, but apparently jQuery's then method apparently doesn't properly unwrap $q promises even though $q promises have their own then method.