understanding setInterval in javascript - javascript

I have a function which does something async like saving to database. Want a mechanism that first inserts the row and the next insertion should occur only when the first insert operation has finished.
Here is what I have tried and it somewhat works.
var interval = true;
function insert() {
model.save(function () {
interval = true;
})
}
foreach(row, function (key, val) {
var interval1 = setInterval(function () {
if (interval) {
insert();
interval = false;
clearInterval(interval1);
}
}, 100)
})
Is it the correct approach of doing this? Please shed some light about my understanding of timers in javascript.

No, you should not be creating timers to poll for when something is done. That's probably the worst way you can do it. What you want to do is to explicitly start the next iteration each time the previous one finishes.
Here's the general idea for how you do this without polling. The idea is that you need to create a function that can be called successive times and each time it's called, it will perform the next iteration. You can then call that function from the completion handler of your async operation. Since you don't have a nice convenient foreach loop to control the iteration, you then have to figure out what state variables you need to keep track of to guide each iteration. If your data is an array, all you need is the index into the array.
function insertAll(rows) {
// I'm assuming rows is an array of row items
// index to keep track of where we are in the iteration
var rowIndex = 0;
function insert() {
// keep going as long as we have more rows to process
if (rowIndex < rows.length) {
// get rows[rowIndex] data and do whatever you need to do with it
// increment our rowIndex counter for the next iteration
++rowIndex;
// save and when done, call the next insert
model.save(insert)
}
}
// start the first iteration
insert();
}
If you don't have your data in an array that is easy to step through one at a time this way, then you can either fetch each next iteration of the data when needed (stopping when there is no more data) or you can collect all the data into an array before you start the operation and use the collected array.

No, this is absolutely not the right way to do this. Lets assume that row contains 10 values, then you are creating 10 independent timers which continuously run and check whether they can insert. And it's not even guaranteed that they are executed in the order they are created.
As jfriend00 already mentioned, you should omit the "loop" and make use of the completion callback of the save operation. Something like this:
var rows = [...];
function insert(rows, index) {
index = index || 0;
var current_element = rows[index];
model.save(function() {
if (index < rows.length - 1) {
insert(rows, index + 1);
}
});
}
insert(rows);
Notice how the function calls itself (somehow) after the save operation is complete, increasing the index so the next element in the array is "saved".

I would use a library that handles async stuff such as async.js
BTW it seems like your model.save methods takes a callback, which you can use directly to call the insert method. And if the insert function is one you have made by yourself, and not a part of some bigger framework, I will suggest to re-write it and make take a callback as parameter, and use that instead of using setInterval for checking when async work is done.

Related

Assigning each element of an array (one at a time) to a variable for a specific amount of time

I have an array with a list of elements(users in this case ).I have a variable called currentBeneficiary. I want to assign each user to be a currentBeneficiary for a specific amount of time like 10m,then we move on to the next member of array and assign them to the variable(currentBeneficiary) for same amount of time so on. I have used SetInterval() and it assigns an element of the array to the variable just for 1s after the specified period of time.And as mentioned i just want the vice versa.How can i achieve this kindly?
const members = merrygoround.members
let currentbeneficiary
if(merrygoround.interval === "daily"){
function DelayArray(array, delegate, delay) {
var i = 0
var interval = setInterval(function() {
delegate(array[i]);
if (i++ >= array.length - 1)
clearInterval(interval);
}, delay)
return interval
}
DelayArray(members, function(obj) {
currentbeneficiary = obj
console.log(`current beneficiary is ${currentbeneficiary}`)
},1000*60)
}
setInterval mean process will wait a "delay" before run your logic, after that wait a "delay" again. I am not sure I understand your question. I guess you want the first user must run right the moment DelayArray call, so you just call the delegate function for the first user before run DelayArray, variable i will start with 1.

Append items ordering by placed amount

I'm using this function to append new items in order by the amount. This function is being called every 30-50ms.
var insertBefore = false;
container.find('.roll-user-row[data-user-id="' + user_data.id + '"]').remove();
container.children().each(function () {
var betContainer = $(this), itemAmount = $(this).attr('data-amount'), betId = $(this).attr('data-user-id');
if (itemAmount < betData.totalAmount) {
insertBefore = betContainer;
return false;
}
});
if (insertBefore) {
$(template).insertBefore(container);
} else {
container.prepend(template);
}
itemAmount = $(this).attr('data-amount') is integer, betData.totalAmount is interger too. And if appending goes slower than ±300ms - everything works well. In case of fast appending I get this result:
and thats not even close what I want - thats random. How to solve this?
1. Refactoring
First of all, return within .each callback doesn't work. It just breaks current iteration, not all the cycle. If you want to interrupt cylce, you should use simple for-loop and break statement. Then, I would recommend to call $() as rarely as possible, because this is expensive. So I would suggest the following refactoring for your function:
function run() {
container.find('.roll-user-row[data-user-id="' + user_data.id + '"]').remove();
var children = container.children();
for (var i = 0; i < children.length; i++) {
var betContainer = $(children[i]); // to cache children[i] wrapping
var itemAmount = betContainer.attr('data-amount');
var betId = betContainer.attr('data-user-id');
if (itemAmount < betData.totalAmount) {
$(template).insertBefore(container);
return; // instead of "break", less code for same logic
}
}
container.prepend(template); // would not be executed in case of insertBefore due to "return"
}
2. Throttling
To run a 50ms repeating process, you are using something like setInterval(run, 50). If you need to be sure, that run is done and this is 300ms delay, then you may use just setInterval(run, 300). But if the process initializes in a way that you can't change, and 50ms is fixed interval for that, then you may protect run calling by lodash throttle or jquery throttle plugin:
var throttledRun = _.throttle(run, 300); // var throttledRun = $.throttle(300, run);
setInterval(throttledRun, 50);
setInterval is just for example, you need to replace your initial run with throttled version (throttledRun) in your repeater initialization logic. This means that run would not be executed until 300ms interval has passed since the previous run execution.
I am only posting the approach here, if my understanding is right, then I'll post a code. First thing came to my mind reading this was the 'Virtual DOM' concept. Here is what you can do,
Use highly frequent random function calls only to maintain a data structure like an object. Don't rely on DOM updates.
Then use a much less frequent setInterval repetitive function call to redraw (or update) your DOM from that data structure.
I am not sure there are any reason you can't take this approach, but this will be the most efficient way to handle DOM in a time critical use-case.

For Loop Iteration Happening all at Once Rather than By Delay

For one of my elements on my page, I want the text to change every ten seconds, and for the class to be changed. Text changing is easy, as is class changing, but I'm having trouble with my for loop, and I feel like I'm missing something.
I want to have the for loop choose a random faction in an array, and then apply that to the element. For my testing, I've been using console.log rather than DOM manipulation.
First, I set up my array:
var factions = ["Enforcers", "Reapers", "Ular Boys", "Roaches"];
Then, I want a variable that is a number chosen at random in reference to this array:
var x = factions[Math.floor(Math.random()*factions.length)];
From that, I want the ability to run the Math.floor and Math.random functions elsewhere.
function reDefine() {
x = factions[Math.floor(Math.random()*factions.length)];
console.log(x);
}
Finally, I want the for loop to run 200 times (I've chosen 200 times because it's far and beyond the time the user will be staying on the site), so I told it to count to 200 (i = 0; i < 200). After that, I wanted each time it iterated, to wait 10s, so I have a Timeout function with a delay of 10000 (milliseconds). Then, the code to reDefine and then, in the case of testing, console.log the new definition of the x variable.
function reChange() {
for (var i = 0; i < 200; i++) {
setTimeout(function() {
reDefine();
console.log("Chosen faction is now: " + x);
}, 10000);
}
}
Instead of counting to 1 (the first iteration), waiting 10000, and then redefining x, it redefines x two hundred times, then logs them all.
Is there something I'm specifically doing wrong here, perhaps with the Timeout function?
Is there something I'm specifically doing wrong here, perhaps with the Timeout function?
Yes! You're scheduling a bunch of deferred callbacks, but not actually waiting until one has finished to schedule the next.
You can fix that with something as simple as:
function reChange(currentIndex) {
setTimeout(function() {
reDefine();
console.log("Chosen faction is now: " + factions[currentIndex]);
// If we haven't gotten to the end of the list, queue up another one
var nextIndex = ++currentIndex;
if (nextIndex < factions.length) {
// Enqueue the next faction
reChange(nextIndex);
}
}, 10000);
}
Make sure to note that the function without the timeout has closure over the value of currentIndex for each call of reChange. That is, the next invocation does not replace currentIndex in any previous timeout, since primitives (including numbers) are passed by value. Closure in JS can be a tricky thing.
The core problem is that your execution right now looks like:
for each item
wait
log
rather than:
for the current item
wait
log
repeat
Because JS is single-threaded (for most intents and purposes), setTimeout adds a callback to be executed later. It doesn't block until the timeout has expired, like a traditional sleep would do.

how can I call a recursive function that calls more than 50k times in javascript?

I know the Browser Javascript Stack size limit, but is there any way to break it? while I am reading Javascript Stack size limit in net, I can not find a way to overcome the browser Stack size limit. Is there any tricks to do it in java-script or angular.
for example
var i=0;
rec(para);
function rec(para){
if(para>=50000){
}
else{
$scope.items.push(i);
rec(i++)
}
}
is it possible to add 50000 data into an array..
Use asynchronous means
function func1(n, callback)
{
//do some work
//check for exit condition
if (n == 1)
{
callback();// invoke callback handler and pass the results to it.
}
else
{
setTimeout(function(){
func1(); //call it again with modified parameters
});
}
}
One downside is that you won't be able to return the value from this method anymore.
is it possible to add 50000 data into an array..
Yes, by Iteration. In your case, you simply need to do
function rec()
{
for( var counter = 0; counter < 50000; counter++ )
{
$scope.items.push(counter);
}
}
If your environments allows it, try to stick with native ES5/6 methods. You can add huge consecutive sequence of numbers into array using fill and map in a single step:
// assuming $scope.items is already initialized array
$scope.items = $scope.items.concat((Array(50000).fill(0).map(function(e,i){return i})));
http://www.2ality.com/2013/11/initializing-arrays.html, Array.fill, Array.map.

Javascript - passing index to setInterval callback

The problem
I'm trying to simplify a long javascript code and i have a problem with identifying callbacks.
I have a large array with elements to animate on page
[selector, activate interval, hide after]:
things_to_move = [
['.ufo, .chefoven, .sushi', 9900, 2000],
['.hotdog,.pizzaman,.boyballon', 12090, 3600],
(...)
]
Basically, the aim is to to activate each of the selectors every x seconds, and hide them x seconds later, as per the example above.
Current code
After many tries, I ended up with this:
// Activate the element, and set timeout to hide it
var showFun = function(index1) {
$(things_to_move[index1][0]).addClass('move');
setTimeout( function(){hideFun(index1)},things_to_move[index1][2]);
}
// Hide the element
var hideFun = function(index2) {
$(things_to_move[index2][0]).removeClass('move');
}
// Loop through all items and set the interval for each one
for(_A=0; _A < things_to_move.length; _A++) {
setInterval(function(){showFun(_A)}, things_to_move[_A][1]);
}
But of course this doesn't work. Every time the showFun function is called, it takes the value of _A after the loop finished and not the value at which setInterval was set.
Question
So the question is, how can i pass a unique index into the setInterval callback, so the callback knows which array item to use?
Final solution
If anyone is interested, the final solution: Fiddle
The most direct way to solve it is using closures.
Try something like this:
for(_A=0; _A < things_to_move.length; _A++) {
setInterval((function(_innerA){
return function(){ showFun(_innerA); };
})(_A), things_to_move[_A][1]);
}

Categories

Resources