简体   繁体   中英

AngularJS multiple Controllers with one Service: Asynchronous errors

I am getting some asynchronous errors when executing a function on my controllers near-simultaneously. Each controller takes some data, and calls a method in a service for testing. The service returns a promise to the controller, manipulates the data passed in, then resolves the promise. The code outline for the service looks like so:

<!-- language: lang-js -->
//Service that our controller can access
app.service("testing", function($timeout, $q) {

  //Test function which takes a group, and returns a promise with the result
  this.Test = function(resultsLocation, testList, testFunction) {
    //promise we are returning
    var deferred = $q.defer();
    var i = 0;

    //TestCallback loop
    TestCallBack = function(testList) {
        if (i < testList.length) {

            //perform a test on one item of the list
            testFunction(testList[i]).then(function() {
                //push result back to controller
                resultsLocation.push(testList[i].result);
                i++;

                //show result of that one item with scope update.
                //also looks visually pleasing to see test come in
                //one at a time
                $timeout(function() {
                    TestCallBack(testList);
                }, 100);
            });
        } else {
            //we are done. Resolve promise
            deferred.resolve("Done");
        }
    };

    //initiate loop
    TestCallBack(testList);

    //return promise
    return deferred.promise;
  };

});//testing Service

And then I have a few controllers that roughly look like this:

<!-- language: lang-js -->
//Peripheral
app.controller("peripheral#", function($scope, testing) {
    //self stuff
    $scope.Title = "Peripheral#";
    $scope.Summary = "";
    $scope.Results = new Array();

    //initial lin tests
    var DiagnosticsList = [
        //test1
        //test2
        //etc...
    ];

    //Tests routine
    $scope.Testing = function() {
        //reset results
        $scope.Results = new Array();
        $scope.Summary = "Testing...";

        //Do Tests
        testing.Test($scope.Results, DiagnosticsList, CustomTestingFunction1).then(
            function(result) {
                $scope.Summary = "Testing...";
            }, 
            function(error) {
                console.log("Error testing Peripheral1");
            }
        );
    };
});

"Testing" is called on a button press in the html. The problem is if controller1 calls "Testing", then controller2 calls "Testing", the promise will never be resolved in controller1. Worse still, some of the test results are pushed into controller 2's results.

Perhaps I'm missing something, but I could've sworn I read somewhere that a service will be it's own instance when a controller has it.

Anyways, here's a plunker demonstrating the behavior: https://plnkr.co/edit/fE5OD35LaXHWrhv0ohq2?p=preview

Pressing "Test" individually is fine, but if you press "Test" while the other controller is testing, you'll get odd behavior such as values being mixed up, and the first controller will never finish testing.

The problem is that the service assigns a global variable, TestCallBack , to a reference to a local anonymous function. Instead declare it as a named function on the service function scope.

app.service("testing", function($timeout, $q) {

    //Test function which takes a group, and returns a promise with the result
    this.Test = function(resultsLocation, testList, testFunction) {
        //promise we are returning
        var deferred = $q.defer();
        var i = 0;

        //TestCallback loop
    //DONT use global variable
    //TestCallBack = function(testList) {
    //INSTEAD use a named function
    function TestCallBack(testlist) {
            if (i < testList.length) {

The DEMO on PLNKR


So just to clarify, declaring a function as a var puts it on the global scope even when declared inside a function?

The error was setting a value to an undeclared variable.

Assigning a value to an undeclared variable implicitly creates it as a global variable (it becomes a property of the global object) when the assignment is executed. The differences between declared and undeclared variables are:

  1. Declared variables are constrained in the execution context in which they are declared. Undeclared variables are always global.

  2. Declared variables are created before any code is executed. Undeclared variables do not exist until the code assigning to them is executed.

  3. Declared variables are a non-configurable property of their execution context (function or global). Undeclared variables are configurable (eg can be deleted).

Because of these three differences, failure to declare variables will very likely lead to unexpected results. Thus it is recommended to always declare variables, regardless of whether they are in a function or global scope. And in ECMAScript 5 strict mode, assigning to an undeclared variable throws an error. 1

In this case, when the service.Test function was called the second time, the global value TestCallBack was being replaced by a reference to the anonymous function in the second instantiation of the service.Test function. The anonymous TestCallBack function refers to a closure 2 of the service.Test function. A closure is a special kind of object that combines two things: a function, and the environment in which that function was created. So functions scheduled by the first instantiation were getting their i and resultsLocation switched to the closure of the second instantiation of the service.Test function.

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