Loopback Callback inside For-Loop - javascript

In a for-loop I call a loopback function (model.find()) and use the callback method. In this method I need the i variable of the for-loop to specify a variable but it is not accessible from closure.
I already tried writing (i) or call(this,i) behind the callback function but it didn't work.
for (var i = 0; i < $scope.objects.length; i++) {
Priority.find({
filter: {
where: {priority: $scope.selectedPriority[i].priority}
}
}, function (prios) {
Priority.create({"priority": $scope.selectedPriority[i].priority //i is not accessible
}, function (priority) {
$scope.selectedPriority[i].priority = undefined; //i is not accessible
}, function (error) {
console.log(error);
});
}
});
}

Actually "i" should be defined but you'll always find it at its highest value (= $scope.objects.length - 1), the reason is that since Priority.find is asynchronous, once it returns an answer, the for loop has already done iterating.
To solve it, you could put body of the loop inside a function:
function find(i) {
Priority.find({
filter: {
where: {priority: $scope.selectedPriority[i].priority}
}
}, function (prios) {
Priority.create({"priority": $scope.selectedPriority[i].priority //i is not accessible
}, function (priority) {
$scope.selectedPriority[i].priority = undefined; //i is not accessible
}, function (error) {
console.log(error);
});
}
});
}
So then, the for loop becomes:
for (var i = 0; i < $scope.objects.length; i++) {
find(i);
}
essentially, you're "capturing" the value of "i" and send it as a function argument, as long as you're in the context of a specific function call, i stays fixed.

Related

Cannot get the current value returned by $http.get when setting $scope variable, only the previous value

I have an angular view that has a table of rows consisting of a select list and an text box. When a select list index is changed, I need to update the corresponding text box on the same row with a lookup value from the database. I am using ng-Change on the select list to call a $scope function that utilizes $http.get to make the call through an ActionMethod. I have tried this in a million ways, and finally was able to extract a value from the $http.get function by assigning it to a scope variable, but I only ever get the value of the previous lookup triggered by the selected index change, not the current one. How can I get a value real-time? I understand it is asynchronous, so I know the nature of the problem. How do I work around it? Current state of my .js:
$scope.EntityId = null;
$scope.EntityNameChanged = function (item, block) {
for (var i = 0; i < block.length; i++)
{
if (item.Value == block[i].Name.Value) {
$scope.GetEntityId(item.Value);
block[i].Id = $scope.EntityId;
}
}
}
$scope.GetEntityId = function(name) {
$http.get("EntityId", { params: { EntityName: name } }).then(function success(response) {
$scope.EntityId = response.data[0].Value;
});
};
The GetEntityID function should return a promise
function GetEntityId(name) {
//save httpPromise
var p = $http.get("EntityId", { params: { EntityName: name } });
//return derived promise
return p.then(function onSuccess(response) {
//return chained data
return response.data[0].Value;
});
};
Then use an IIFE in the for loop.
$scope.EntityNameChanged = function (item, block) {
for (var i = 0; i < block.length; i++) {
//USE IIFE to hold value of i
(function IIFE(i) {
if (item.Value == block[i].Name.Value) {
//save promise
var p = GetEntityId(item.Value);
//extract value from promise
p.then(function onSuccess(Value) {
block[i].Id = Value;
});
}
})(i);
}
}
Because the onSuccess function gets invoked asynchronously after the for loop completes, an IIFE closure is necessary to preserve the value of i until after the data is returned from the server.
Your GetEntityId function is not async, even though it makes an async request. by the time it sets $scope.EntityId, the for loop has already exited.
You can't actually queue up async calls like this, because each one of them is trying to share a value outside the loop that could be set by any other iteration, so one item in the loop might get another item's return value.
Instead, you should return the promise back to the loop, and perform your .then in the loop. Something like the following:
$scope.EntityNameChanged = function(item, block) {
for (var i = 0; i < block.length; i++) {
if (item.Value == block[i].Name.Value) {
$scope.GetEntityId(item.Value)
.then(function success(response) {
block[i].Id = response.data[0].Value;
});
}
}
}
$scope.GetEntityId = function(name) {
return $http.get("EntityId", {
params: {
EntityName: name
}
});
};
(note this is untested, but should do what you expect).
To prompt Angular to update the value of the scope on its $watch loop, call $scope.apply() after assignment. This should bring you to the most recent value.
EDIT: This answer was wrong. It was a $http get request, which already uses $apply.
What you need to do is put the request inside a factory and return a promise. Require the factory in your controller.
app.factory('getData', function ($http, $q){
this.getlist = function(){
return $http.get('mylink', options)
.then(function(response) {
return response.data.itemsToReturn;
});
}
return this;
});
app.controller('myCtrl', function ($scope, getData){
app.getData()
.then(function(bar){
$scope.foo = bar;
});
});

"ESLint no-loop-func rule" What to do when loop variables are required in a callback

The company where I work for requires us to follow the no-loop-func ES-lint rule. I am in a situation where a loop variable is required in a callback.
An example can be found bellow:
var itemsProcessed = 0;
for (var index = 0; index < uniqueIdentifiers.length; index++) {
let uniqueIdentifier = uniqueIdentifiers[index];
// ESLint: Don't make functions within a loop (no-loop-func)
var removeFileReferenceCallback = function (removeReferenceError) {
if (removeReferenceError !== null) {
NotificationSystem.logReferenceNotRemoved(uniqueIdentifier, undefined);
}
// When all items are removed, use the callback
if (++itemsProcessed === uniqueIdentifiers.length) {
callback(null);
}
};
// Remove the reference
this.database.removeFileReference(uniqueIdentifier, removeFileReferenceCallback);
}
How can the code be refactored so that the rule can be met?
Just don't use loops. Iterator methods are so much better.
uniqueIdentifiers.forEach(function(uid) {
this.database.removeFileReference(uid, function (err) {
if (err) {
NotificationSystem.logReferenceNotRemoved(uid, undefined);
}
});
}, this);
callback(null);
To call a callback after everything has been completed, you're going to need something like this:
var db = self.database;
var promises = uniqueIdentifiers.map(function(uid) {
return new Promise(function (res) {
db.removeFileReference(uid, function (err) {
if (err) {
NotificationSystem.logReferenceNotRemoved(uid, undefined);
}
res();
});
});
});
Promise.all(promises).then(callback);
I use ESList for all my JavaScript and I adhere to the no-loop-func rule, it will bite you badly if you don't. The reason is well explained here: JavaScript closure inside loops – simple practical example.
I suggest the other answers in terms of refactors.
Could using bind solve your problem?
var itemsProcessed = 0;
for (var index = 0; index < uniqueIdentifiers.length; index++) {
let uniqueIdentifier = uniqueIdentifiers[index];
// Remove the reference
this.database.removeFileReference(uniqueIdentifier, removeFileReferenceCallback.bind(uniqueIdentifier));
}
function removeFileReferenceCallback(removeReferenceError) {
if (removeReferenceError !== null) {
NotificationSystem.logReferenceNotRemoved(this, undefined);
}
// When all items are removed, use the callback
if (++itemsProcessed === uniqueIdentifiers.length) {
callback(null);
}
};
I also converted my object into array and then used forEach.

Javascript - Handling race condition on async function call

This weekend, I ran into a peculiar problem of handling race conditions in Javascript.
Here is the code that was giving me problem:
function myFunction(requestObj, myArray) {
for(var i=0;i<myArray.length;i++) {
//AJAX call
makeAjaxCall(requestObj, function(data) {
//Callback for the ajax call
//PROBLEM : HOW CAN I ACCESS VALUE OF 'i' here for the iteration for which the AJAX call was made!!!
}
}
}
Accessing the value of 'i' inside the AJAX callback would not give me the expected value because by the time AJAX call response comes back, the 'for' loop would have crossed many more iterations.
To handle this, I used the following approach:
function myFunction(requestObj, myArray) {
var i = 0;
function outerFunction() {
makeAjaxCall(requestObj, innerFunction);
}
function innerFunction(data) {
i++;
if(i<myArray.length) {
outerFunction();
}
}
outerFunction();
}
Is this the correct approach? Any other way I can improve this assuming it is a 3rd pary AJAX library call which I can't modify.
You just need to use a closure:
function myFunction(requestObj, myArray) {
for(var i=0;i<myArray.length;i++) {
//AJAX call closed over i
(function(i) { // wrap your call in an anonymous function
makeAjaxCall(requestObj, function(data) {
// i is what you think it is
}
})(i) // pass i to the anonymous function and invoke immediately
}
}
The issue is that the callbacks you're passing to the ajax call have an enduring reference to i, not a copy of its value when they were created.
Your approach is fine except that it waits to make the second ajax call until the first finishes, then waits for the second to finish before the third, etc. Unless you have to do that (and I get the impression you don't), it's better to let them overlap.
A couple of options:
Use a builder function:
function myFunction(requestObj, myArray) {
for(var i=0;i<myArray.length;i++) {
//AJAX call
makeAjaxCall(requestObj, buildHandler(i));
}
function buildHandler(index) {
return function(data) {
// Use `index` here
};
}
}
Now, the handler has a reference to index, which doesn't change, rather than i, which does.
Use Function#bind:
function myFunction(requestObj, myArray) {
for(var i=0;i<myArray.length;i++) {
//AJAX call
makeAjaxCall(requestObj, function(index, data) {
// Use index here
}.bind(null, i));
}
}
Function#bind creates a function that, when called, will call the original function with a specific this value (we're not using that above) and any arguments you pass bind — followed by any arguments given to the bound function.
I prefer #1: It's clear to read and doesn't create a bunch of unnecessary functions (whereas in theory, #2 creates two functions per loop rather than just one).
There are two other ways of doing this: using bind, using a closure
Using bind:
function myFunction(requestObj, myArray) {
for(var i=0;i<myArray.length;i++) {
makeAjaxCall(requestObj, function(idx,data) {
//idx will be correct value
}.bind(null,i));
}
}
Using a closure:
function myFunction(requestObj, myArray) {
for(var i=0;i<myArray.length;i++) {
(function(idx){
makeAjaxCall(requestObj, function(data) {
//idx will be correct value
});
})(i);
}
}
There is also third method, use another function to create your callback
function myFunction(requestObj, myArray) {
function makeCB(idx){
return function(){
//do stuff here
}
}
for(var i=0;i<myArray.length;i++) {
makeAjaxCall(requestObj, makeCB(i));
}
}

How to pass the 'callbacks' array argument to an anonymous function?

I have a member function in the object which gets the array of callback functions and the name of the event for which this function is set:
...
setHandlesByList: function (list) {
for (var i in list) {
var self = this;
$(document).on(list[i].name, function (e) {
list[i].callBack.call(self,e)
});
};
},
...
Somewhere in the child objects I have a call to this function of the parent object:
...
initClass: function () {
this.setHandlesByList([
{ name: 'configChecked', callBack: onConfigChecked },
{ name: 'configExpired', callBack: onConfigExpired },
]);
},
onConfigChecked: function() {
// some code
},
onConfigExpired: function() {
// some code
},
....
but something goes wrong - for all events the handler is the last set callback function...
Try the following:
setHandlesByList: function (list) {
for ( var i = 0; i < list.length; i++ ) {
addCallback(list[i].name, list[i].callback);
}
function addCallback(on, name, callback) {
$(document).on(name, function(e) { callback.call(on, e); });
}
},
There is a problem with your scoping, because the value of i eventually ends up being the last value of i when your callbacks are evaluated.
Also note that you could use list.forEach.
Each event handler function you create in this code:
setHandlesByList: function (list) {
for (var i in list) {
var self = this;
$(document).on(list[i].name, function (e) {
list[i].callBack.call(self,e)
});
};
},
...has an enduring reference to list and i, not copies of them as of when the function is created. Since i ends up being the last property enumerated, all handlers end up referring to the same list entry.
Instead, create a builder function to create the callback (sorry, I can't recreate the indentation style you use, I've just used a fairly standard one):
setHandlesByList: function (list) {
var self = this;
for (var i in list) {
$(document).on(list[i].name, buildHandler(list[i]));
};
function buildHandler(entry) {
return function (e) {
entry.callBack.call(self,e)
};
}
},
Now, the function created closes over entry, the argument to the buildHandler call, rather than over list and i. Since entry (the argument) doesn't change, the handler works.
Note also that I've moved the var self = this; out of the loop, as it didn't vary from iteration to iteration and so had no business being in the loop.
Side note: You've said that the function receives an array. If so, for-in (with no safeguards) is not the correct way to loop through the entries in that array. More: Myths and realities of for..in

I am using Async's Library Parallel method wrong? But how?

I am using the async library's parallel function, but I am running into a weird problem. I loop through an array of objects to create an array of functions called people. People gets passed into async's parallel method to execute in parallel. However, when I take a look at the parameter "people" that actually get passed into the "printFunction" it is wrong. The people parameter is always the last object in my people array.
So in this example, the console log in printFunction would print
{ 'name' : 'jar jar binks' }
{ 'name' : 'jar jar binks' }
Code:
var people = [{ 'name' : 'obi wan kenobi'}, { 'name' : 'jar jar binks' }];
// Create an array of tasks to be processed.
for(var i = 0; i < people.length; i++) {
tasks.push( function(callback) {
setTimeout( makePrintFunction(people[i], callback), 200);
});
}
// Process those tasks.
async.parallel(tasks, function(err, stuff) {
// ...do some stuff with the returned array here.
});
Make Print Function:
function makePrintFunction(people, next) {
return validateAndTrackFlag(people, next);
}
function printFunction(people, next) {
console.log(people); // Always prints: { 'name' : 'jar jar binks' }
next(null, true)
}
Obviously this is not the real code, I just changed the names and objects.
Thanks for any help in advance.
This is not an async problem, it's a closure problem.
Try this:
for(var i = 0; i < people.length; i++) {
(function (i){
tasks.push( function(callback) {
setTimeout( makePrintFunction(people[i], callback), 200);
});
})(i)
}
Or this:
people.forEach (function (p){
tasks.push( function(callback) {
setTimeout( makePrintFunction(p, callback), 200);
});
});
You are dealing with classical scoping issue. Try this:
for(var i = 0; i < people.length; i++) {
(function(i) {
tasks.push( function(callback) {
setTimeout( makePrintFunction(people[i], callback), 200);
});
})(i);
}
or even better
var create_scope = function(i) {
tasks.push( function(callback) {
setTimeout( makePrintFunction(people[i], callback), 200);
});
};
for (var i = 0; i < people.length; i++) {
create_scope(i);
}
or the best:
people.forEach(function(person) {
tasks.push( function(callback) {
setTimeout( makePrintFunction(person, callback), 200);
});
});
This happens because for loop does not create a scope, i.e. when functions are fired i is the last index. Now functions do create scope, so inside anonymous function i is the expected value.

Categories