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:
Declared variables are constrained in the execution context in which they are declared. Undeclared variables are always global.
Declared variables are created before any code is executed. Undeclared variables do not exist until the code assigning to them is executed.
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.