Remove from datatable after callback is finished - javascript

I have a datatable, a checkbox on each table, and a button that will trigger my operation on that row. I would like to remove that row when my operation is done.
for (i = 0; i < checkedBoxes.length; i++) {
var chk = checkedBoxes[i];
var tdef = chk.closest("tr").querySelectorAll('td');
var myThing = tdef[1].innerHTML;
service.doSomething(myThing, function (result) {
service.doSomethingElse();
// I would like to remove this row once I'm done with this row
//browseDataTable.row($(chk).parents('tr')).remove().draw();
});
}
I know that I'm not supposed to remove that row as I'm looping through it. So I'm planning to just collect the index of each row, and when everything is finished, I can remove it, like this:
var myArr = new Array();
for (i = 0; i < checkedBoxes.length; i++) {
service.doSomething(myThing, function (result) {
service.doSomethingElse();
myArr.push(i);
}) // Chrome said 'then' is undefined, so how do I chain callback here?
.then(function () {
// Remove all rows at index in myArr
});
}
The service isn't async service, it's an ASMX service.

You are using your service both like a function with a callback and a Promise. So which is it? Does it take a callback, or does it return a Promise?
It looks like it does not return a Promise, because you are trying to chain .then() and it's undefined.
The service isn't async then why are you giving it a callback and trying to chain a .then(), if it's synchronous?
Anyway, one easy way to solve your issue is to use let, which will create a scope for every loop.
Currently :
for (i = 0; i < checkedBoxes.length; i++) { // i is a global (window) variable, that's bad
service.doSomething(myThing, function (result) {
service.doSomethingElse();
myArr.push(i); // i will always be checkboxes.length
})
}
By using let :
for (let i = 0; i < checkedBoxes.length; i++) { // i is in the local scope
service.doSomething(myThing, function (result) {
service.doSomethingElse();
myArr.push(i); // the value of i will be different (correct) each time
})
}

Related

Best way to populate array object with another dataset [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 4 years ago.
The community reviewed whether to reopen this question 3 months ago and left it closed:
Duplicate This question has been answered, is not unique, and doesn’t differentiate itself from another question.
I am running an event loop of the following form:
var i;
var j = 10;
for (i = 0; i < j; i++) {
asynchronousProcess(callbackFunction() {
alert(i);
});
}
I am trying to display a series of alerts showing the numbers 0 through 10. The problem is that by the time the callback function is triggered, the loop has already gone through a few iterations and it displays a higher value of i. Any recommendations on how to fix this?
The for loop runs immediately to completion while all your asynchronous operations are started. When they complete some time in the future and call their callbacks, the value of your loop index variable i will be at its last value for all the callbacks.
This is because the for loop does not wait for an asynchronous operation to complete before continuing on to the next iteration of the loop and because the async callbacks are called some time in the future. Thus, the loop completes its iterations and THEN the callbacks get called when those async operations finish. As such, the loop index is "done" and sitting at its final value for all the callbacks.
To work around this, you have to uniquely save the loop index separately for each callback. In Javascript, the way to do that is to capture it in a function closure. That can either be done be creating an inline function closure specifically for this purpose (first example shown below) or you can create an external function that you pass the index to and let it maintain the index uniquely for you (second example shown below).
As of 2016, if you have a fully up-to-spec ES6 implementation of Javascript, you can also use let to define the for loop variable and it will be uniquely defined for each iteration of the for loop (third implementation below). But, note this is a late implementation feature in ES6 implementations so you have to make sure your execution environment supports that option.
Use .forEach() to iterate since it creates its own function closure
someArray.forEach(function(item, i) {
asynchronousProcess(function(item) {
console.log(i);
});
});
Create Your Own Function Closure Using an IIFE
var j = 10;
for (var i = 0; i < j; i++) {
(function(cntr) {
// here the value of i was passed into as the argument cntr
// and will be captured in this function closure so each
// iteration of the loop can have it's own value
asynchronousProcess(function() {
console.log(cntr);
});
})(i);
}
Create or Modify External Function and Pass it the Variable
If you can modify the asynchronousProcess() function, then you could just pass the value in there and have the asynchronousProcess() function the cntr back to the callback like this:
var j = 10;
for (var i = 0; i < j; i++) {
asynchronousProcess(i, function(cntr) {
console.log(cntr);
});
}
Use ES6 let
If you have a Javascript execution environment that fully supports ES6, you can use let in your for loop like this:
const j = 10;
for (let i = 0; i < j; i++) {
asynchronousProcess(function() {
console.log(i);
});
}
let declared in a for loop declaration like this will create a unique value of i for each invocation of the loop (which is what you want).
Serializing with promises and async/await
If your async function returns a promise, and you want to serialize your async operations to run one after another instead of in parallel and you're running in a modern environment that supports async and await, then you have more options.
async function someFunction() {
const j = 10;
for (let i = 0; i < j; i++) {
// wait for the promise to resolve before advancing the for loop
await asynchronousProcess();
console.log(i);
}
}
This will make sure that only one call to asynchronousProcess() is in flight at a time and the for loop won't even advance until each one is done. This is different than the previous schemes that all ran your asynchronous operations in parallel so it depends entirely upon which design you want. Note: await works with a promise so your function has to return a promise that is resolved/rejected when the asynchronous operation is complete. Also, note that in order to use await, the containing function must be declared async.
Run asynchronous operations in parallel and use Promise.all() to collect results in order
function someFunction() {
let promises = [];
for (let i = 0; i < 10; i++) {
promises.push(asynchonousProcessThatReturnsPromise());
}
return Promise.all(promises);
}
someFunction().then(results => {
// array of results in order here
console.log(results);
}).catch(err => {
console.log(err);
});
async await is here
(ES7), so you can do this kind of things very easily now.
var i;
var j = 10;
for (i = 0; i < j; i++) {
await asycronouseProcess();
alert(i);
}
Remember, this works only if asycronouseProcess is returning a Promise
If asycronouseProcess is not in your control then you can make it return a Promise by yourself like this
function asyncProcess() {
return new Promise((resolve, reject) => {
asycronouseProcess(()=>{
resolve();
})
})
}
Then replace this line await asycronouseProcess(); by await asyncProcess();
Understanding Promises before even looking into async await is must
(Also read about support for async await)
Any recommendation on how to fix this?
Several. You can use bind:
for (i = 0; i < j; i++) {
asycronouseProcess(function (i) {
alert(i);
}.bind(null, i));
}
Or, if your browser supports let (it will be in the next ECMAScript version, however Firefox already supports it since a while) you could have:
for (i = 0; i < j; i++) {
let k = i;
asycronouseProcess(function() {
alert(k);
});
}
Or, you could do the job of bind manually (in case the browser doesn't support it, but I would say you can implement a shim in that case, it should be in the link above):
for (i = 0; i < j; i++) {
asycronouseProcess(function(i) {
return function () {
alert(i)
}
}(i));
}
I usually prefer let when I can use it (e.g. for Firefox add-on); otherwise bind or a custom currying function (that doesn't need a context object).
var i = 0;
var length = 10;
function for1() {
console.log(i);
for2();
}
function for2() {
if (i == length) {
return false;
}
setTimeout(function() {
i++;
for1();
}, 500);
}
for1();
Here is a sample functional approach to what is expected here.
ES2017: You can wrap the async code inside a function(say XHRPost) returning a promise( Async code inside the promise).
Then call the function(XHRPost) inside the for loop but with the magical Await keyword. :)
let http = new XMLHttpRequest();
let url = 'http://sumersin/forum.social.json';
function XHRpost(i) {
return new Promise(function(resolve) {
let params = 'id=nobot&%3Aoperation=social%3AcreateForumPost&subject=Demo' + i + '&message=Here%20is%20the%20Demo&_charset_=UTF-8';
http.open('POST', url, true);
http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
http.onreadystatechange = function() {
console.log("Done " + i + "<<<<>>>>>" + http.readyState);
if(http.readyState == 4){
console.log('SUCCESS :',i);
resolve();
}
}
http.send(params);
});
}
(async () => {
for (let i = 1; i < 5; i++) {
await XHRpost(i);
}
})();
JavaScript code runs on a single thread, so you cannot principally block to wait for the first loop iteration to complete before beginning the next without seriously impacting page usability.
The solution depends on what you really need. If the example is close to exactly what you need, #Simon's suggestion to pass i to your async process is a good one.

Why is this recurring function in Javascript, not working?

What is wrong with this recurrent function pattern ?
I am getting an Indentation level NaN.
My expectations were that paragraphs and j should be visible for the function next_level
export async function run() {
try {
await OneNote.run( async context => {
var page = context.application.getActivePage();
var pageContents = page.contents;
var firstPageContent = pageContents.getItemAt(0);
var paragraphs=firstPageContent.outline.paragraphs;
//firstPageContent.delete()
//var out_line=firstPageContent.outline
paragraphs.load('richText/text');
var j=1;
// Run the queued commands, and return a promise to indicate task completion.
return context.sync()
.then(async function next_level(paragraphs,j) {
//debugger;
j=j+1;
console.log("indentation level:",j)
console.log("Items",paragraphs.count)
if (paragraphs.count>0){
console.log(paragraphs.items);
for (var i=0; i < paragraphs.items.length; i++)
{
var paragraph= paragraphs.items[i];
paragraph.load('richText/text');
console.log(j,paragraph.richText.text);
next_level(paragraph.paragraphs,j);
}
}
return context.sync()
})
Here's the OneNote context.sync documentation
When context.sync completes and calls next_level for the first time, it has nothing to pass as an argument. According to the documentation, the passThroughValue argument for context.sync is passed into the promise. So, you can use that, or you can to initialize paragraphs and j in the first call.
paragraphs and j are not implicitly passed into next_level. When you access paragraphs in the scope of next_level you are only accessing the local variables. If you want to access the paragraphs and j outside of next_level then you need to use different names.

angular loop request undefined

$http.get("./data/web.json") request succesfully and return a array. When I loop the array by doing request, the iterator variable i would be undefined?! So how can I access the return array and doing the loop request?
<script>
var ngApp = angular.module("webApp", ['xml'])
.config(function ($httpProvider) {
$httpProvider.interceptors.push('xmlHttpInterceptor');
})
.controller("webCtrl", function($scope, $http) {
$http.get("./data/web.json")
.success(function(response) {
$scope.websites = response;
for (i = 0; i < $scope.websites.length; i++){
$http.get('../api/alexa?url=' + $scope.websites[i].url)
.success(function(rsp) {
//$scope.websites[i].rank = rsp.data.POPULARITY.TEXT;
console.log($scope.websites[i]);
});
}
console.log(response);
});
});
</script>
.controller("webCtrl", function ($scope, $http) {
$http.get("./data/web.json")
.success(function (response) {
$scope.websites = response;
for (i = 0; i < $scope.websites.length; i++) {
$scope.processWebsites($scope.websites[i], i);
}
});
$scope.processWebsites = function (website, index) {
$http.get('../api/alexa?url=' + website.url)
.success(function (rsp) {
console.log($scope.websites[index]);
});
}
});
Try this code. This will create a new execution context, thereby removing any unintentional side effects due to async execution.
You want to access i variable but if your request taking to much time then for
loop will not wait it will do ajax call and execute so after for loop
end your i will be $scope.websites.length + 1 (because of i++) so you will get undefined to solve this problem you have to use closure function
JavaScript closure inside loops – simple practical example
var funcs = [];
function createfunc(i) {
return function() {
$http.get('../api/alexa?url=' + $scope.websites[i].url)
.success(function(rsp) {
//$scope.websites[i].rank = rsp.data.POPULARITY.TEXT;
console.log($scope.websites[i]);
});
};
}
$http.get("./data/web.json")
.success(function(response) {
$scope.websites = response;
for (i = 0; i < $scope.websites.length; i++) {
funcs[i] = createfunc(i)
$http.get('../api/alexa?url=' + $scope.websites[i].url)
.success(function(rsp) {
//$scope.websites[i].rank = rsp.data.POPULARITY.TEXT;
});
}
console.log(response);
});
for (i = 0; i < funcs.length; i++) {
funcs[i]();
}
I am not sure How your response json looks like but it should be array of key value pairs or a single key value pair
so if you have like
[ {key:value},{key:value},{key:value}]
as response assuming a key is url in your case
it should directly work for you now you are asking for a key called i
that is website[i] which is undefined.
try doing this
foreach loop
i isn't defined at your second success callback since it is an async callback and when it is been called the parent scope isn't valid anymore since it was executed without defining any local variables, you should define the iterator inside the for loop so it will be proper declared and hoisted
but you should be aware that since it is an async callback you will have a race condition when most of the chances that the loop will end before the first call back will be called and the iterator value in all of the iteration will be the array size (the last value)
var a = [1, 2, 4]
for (var i = 0; i < a.length; i++) {
var that = this;
that.iterator = i;
setTimeout(function() {
alert(a[that.iterator]);
}, 10);
}
I would suggest you to aggregate the calls and use an aggregated callback to handle all of them together using $q.all

Javascript closure access with callbacks inside loop

what shall I do to make the last row of code return a value?
$scope.runActionwithObjects = function() {
for (var i = 0; i < $scope.Objects.length; i++) {
console.log($scope.Objects[i]); //$scope is accessible
$http.get($scope.Objects[i]["Commit"]).success(function (data) {
console.log($scope.Objects[i]);//return undefined
The problem is due to asynchrony of ajax requests.
When the success callback is executed, your loop has already finished and the i variable is already equal to $scope.Objects.length.
Try forEach. This function will create different closures for items in the array.
$scope.Objects.forEach(function(currentObject){
console.log(currentObject); //$scope is accessible
$http.get(currentObject["Commit"]).success(function (data) {
console.log(currentObject);
});
});
The reason your $scope.Objects[i] is undefined because the variable i is always = $scope.Objects.lenth + 1, for example you got 5 elements, the i will be 6, because the at the time of callback, it already got the last value.
One solution is to bind needed object to that method, so we can access it via this(we can not reference directly by closure to ref variable, because it's still stored the last item), for example:
for (var i = 0; i < $scope.Objects.length; i++) {
var ref = $scope.Objects[i];
// console.log($scope.Objects[i]); //$scope is accessible
var successCallback = (function (data) {
console.log(this);//return the ref
}).bind(ref);
$http.get('').success(successCallback);
}
}

Maintain a state of a variable in an asynchronus function

I don't know if there is a term for the thing i ask about, so i'll try to illustrate it:
I Got a function that takes an array of contacts, and loop them through to update their status on the website. The 'something.getStatus' function does not return anything, but upon successful completion, it gives the status (online/offline/etc.) of the user.
The problem here is, when the function inside is called, the variable user_id is the last one in the array for all the function calls, making it impossible to know how have what status.
function gotGroupContacts(contacts) {
for ( i = 0; i < contacts.length; i++) {
var user_id = contacts[i];
something.getStatus(user_id, function(resp) {
updateLinkStatus(user_id, resp.status, resp.statusMessage);
}, getStatusErrorHandler);
}
}
Is there any way i can 'lock' the variable, or otherwise pass it to the function within?
enclose your getStatus method call in a closure, like so
for ( i = 0; i < contacts.length; i++) {
var user_id = contacts[i];
(function(uid) {
something.getStatus(uid, function(resp) {
updateLinkStatus(uid, resp.status, resp.statusMessage);
}, getStatusErrorHandler);
}(user_id))
}
user_id is passed into the anonymous self-executed function, and this ensure to have the right value
if your script doesn't need to be compatible with IE <9,
you can write like this:
function gotGroupContacts(contacts) {
contacts.forEach(function(user_id){
something.getStatus(user_id, function(resp) {
updateLinkStatus(user_id, resp.status, resp.statusMessage);
}, getStatusErrorHandler);
});
}
NOTICE: forEach method can be easily ported to IE, see this: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach

Categories