Cannot set innerHTML inside setTimeout - javascript

Trying to set the innerHTML of a HTML class which are four boxes, each to be set 3 seconds one after another. I can set the innerHTML without setTimeout when the innerHTML is set to a loading icon. When innerHTML is put inside setTimeout the following is returned: 'Uncaught TypeError: Cannot set property 'innerHTML' of undefined'.
Tried to debug my code sending messages to the console and searching stackoverflow but no luck.
var x = document.getElementsByClassName("numberBox");
for (var i = 0; i < 4; i++) {
x[i].innerHTML= '';
x[i].innerHTML= "<div class='loader'></div>"
}
// function to generate array of 4 random numbers
var randomNums = generateRandomNumbers();
for (var i = 0; i < 4; i++) {
setTimeout(function () {
x[i].innerHTML= '';
x[i].innerHTML = randomNums[i];
}, 3000 * i);
}
Would like to know why my innerHTML cannot be set within setTimeout here and possible solutions to my problem.

I believe this is a question of the current scope. The setTimeout function creates its own scope that has no reference to the old variable. You'll likely need to redefine what x is inside the timeout or pass the array explicitly to the timeout.
See here for how-to: How can I pass a parameter to a setTimeout() callback?
I would also recommend reading up on closers as well: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures

When you use var inside the for-loop the setTimeout is actually triggered for the last value of i as in var the binding happens only once.
This is because the setTimeout is triggered when the entire loop is completed, then your i will be 4. Keep in mind that there is a closure because of the callback function you pass in the setTimeout call. That closure will now refer to the final value of i which is 4.
So in this case when the complete loop has executed the value of i is 4 but there are indexes upto 3 in x. That is why when you try to access x[4] you get undefined and you see TypeError to fix this just use let for fresh re-binding with the new value of i in every iteration:
for (let i = 0; i < 4; i++) {
setTimeout(function () {
x[i].innerHTML= '';
x[i].innerHTML = randomNums[i];
}, 3000 * i);
}
Also if you cannot use let due to browser incompatibility you can do the trick with a IIFE:
for (var i = 0; i < 4; i++) {
(function(i){
setTimeout(function () {
x[i].innerHTML= '';
x[i].innerHTML = randomNums[i];
}, 3000 * i);
})(i);
}
This works because var has function scope, so here in every iteration a new scope would be created along with the function with a new binding to the new value of i.

Because of missing i parameter - timeout probably used last (4) which was out of array.
But you can set&use function parameters by adding next timeout parameters.
var x = document.getElementsByClassName("numberBox");
for (var i = 0; i < 4; i++) {
x[i].innerHTML= '';
x[i].innerHTML= "<div class='loader'></div>"
}
// function to generate array of 4 random numbers
var randomNums = generateRandomNumbers();
for (var i = 0; i < 4; i++) {
setTimeout(function (i) {
x[i].innerHTML= '';
x[i].innerHTML = randomNums[i];
}, 3000 * i, i);
}
function generateRandomNumbers() {
var retVal = [];
for (var i = 0; i < 4; i++) {
retVal.push(Math.random());
}
return retVal;
}
<div class="numberBox"></div>
<div class="numberBox"></div>
<div class="numberBox"></div>
<div class="numberBox"></div>

// function to generate array of 4 random numbers
var x = document.getElementsByClassName("numberBox");
var randomNums = generateRandomNumbers();
for (var i = 0; i < 4; i++) {
setTimeout(function () {
x[i].innerHTML= '';
x[i].innerHTML = randomNums[i];
}, 3000 * i, x);
}

This problem is related to a very basic and popular concept of Javascript called closure. It can be solved in at least two ways:
Using let
var x = document.getElementsByClassName("numberBox");
for (let j = 0; j < 4; j++) {
x[j].innerHTML= '';
x[j].innerHTML= "<div class='loader'></div>"
}
// function to generate array of 4 random numbers
var randomNums = generateRandomNumbers();
for (let i = 0; i < 4; i++) {
setTimeout(function () {
x[i].innerHTML= '';
x[i].innerHTML = randomNums[i];
}, 3000 * i);
}
Using IIFE
var x = document.getElementsByClassName("numberBox");
for (var i = 0; i < 4; i++) {
x[i].innerHTML= '';
x[i].innerHTML= "<div class='loader'></div>"
}
// function to generate array of 4 random numbers
var randomNums = generateRandomNumbers();
for (var i = 0; i < 4; i++) {
setTimeout((function (j) {
x[j].innerHTML= '';
x[j].innerHTML = randomNums[j];
})(i), 3000 * i);
}

You are trying to access x in a inner method, that is x is not defined in the scope of setTimeout that is why you receive that execption
I would suggest you use a setInterval function as the solution
Your code:
var randomNums = generateRandomNumbers();
for (var i = 0; i < 4; i++) {
setTimeout(function () {
x[i].innerHTML= '';
x[i].innerHTML = randomNums[i];
}, 3000 * i);
}
A work around
var randomNums = generateRandomNumbers();
let i = 0;
let interval = setInterval(function() {
if( i != 2){
x[i].innerHTML= '';
x[i].innerHTML = randomNums[i];
i += 1;
} else {
clearInterval(interval) ;
}
}, 3000);

This issue appears to be caused by several factors.
Defining the generateRandomNumbers() function outside the setTimeout() scope.
Using the var definition inside your for loop.
function generateRandomNumbers() {
return Math.floor(Math.random() * 999999) + 10000;
}
var x = document.getElementsByClassName("numberBox");
for (var i = 0; i < x.length; i++) {
x[i].innerHTML= '';
x[i].innerHTML= "<div class='loader'></div>"
}
for (let i = 0; i < x.length; i++) {
setTimeout(function() {
x[i].innerHTML= '';
x[i].innerHTML = generateRandomNumbers();
}, 3000 * i);
}
This is a different implementation, but here's a Fiddle

Related

Not inserting element on call inserts after the entire execution

function loadval() {
r = Math.floor(Math.random() * load.length);
console.log(r);
for (let i = 0; i < 9; i++) {
for (let j = 0; j < 9; j++) {
var set = (i + 1) * 10 + j + 1;
var x = document.getElementById(set);
x.value = load[r][i][j];
console.log(load[r][i][j]);
sleep(25);
}
}
}
The element is not inserted one by one after the delay, all the elements are set after the whole function gets executed.
why not use setInterval or setTimeout instead sleep(25)?
// repeat at 2 second intervals
let timerId = setInterval (() => loadval(), 2000);
// stop output after 10 seconds
setTimeout (() => {clearInterval (timerId); alert ('stop');}, 10000);

Why is passing in a variable (as a ref) slower than accessing the same variable defined in a closure?

Given:
(function() {
var items = [1, 2, 3, 4];
// In Chrome, this takes ~8-10 ms to execute.
for(var i = 0; i < items.length; i++) {
x(items);
}
// In Chrome, this takes 1-2 ms to execute.
for(var i = 0; i < items.length; i++) {
y();
}
function x(y) {
y[0] = -100;
}
function y() {
items[0] = 100;
}
})();
Why are the calls to x() 8-10 times slower than the calls to y()? Is it because variable resolution does not need to take place in the execution of y()?
I'm not seeing a difference in time except for the first iteration or 2 which suggests that there is no big difference except something getting added during start up. V8 doesn't optimize immediately AFAIK so that could explain why it takes a few iterations to balance out. Also cache misses.
function log() {
var div = document.createElement("div");
div.textContent = Array.prototype.join.call(arguments, " ");
document.body.appendChild(div);
};
(function() {
var items = new Array(10000000);
for (j = 0; j < 20; ++j) {
var xStart = performance.now();
for(var i = 0; i < items.length; i++) {
x(items);
}
var xDuration = performance.now() - xStart;
var yStart = performance.now();
for(var i = 0; i < items.length; i++) {
y();
}
var yDuration = performance.now() - yStart;
log(j, "x:", xDuration.toFixed(3) + "ms",
"y:", yDuration.toFixed(3) + "ms",
"diff:", (xDuration - yDuration).toFixed(3) + "ms");
}
function x(y) {
y[0] = -100;
}
function y() {
items[0] = 100;
}
})();
body { font-family: monospace; }

Counting numbers in an random array

So I'm trying to find how many of each number from zero to ten is generated in a random array.
I created a random array list
i=0;
var ranList=[];
while (i<20){
i++;
ranList.push(Math.floor(10*Math.random()));
}
//count each number
document.write(ranList.sort().join("<br>"));
/*Then I made a function to count /elements from this array
*/
function ctnumber(array,elem){
var ct=0;
var j =0;
while(j<array.length)
{
j++;
if(array[j]==elem){
ct+=1;}
}
}
return ct;
}
alert(ctnumber(ranList,5));
The second function doesn't execute, any idea why?
Thank you!
First you should avoid using the name array for you variable:
http://www.w3schools.com/js/js_reserved.asp
Your brackets are also wrong. Change your function to this and it should work:
function ctnumber(arr,elem){
var ct=0;
var j =0;
while(j<arr.length)
{
j++;
if(arr[j]==elem){
ct+=1;}
}
return ct;
}
The problem with your code, as stated by Pardeep in his comment, is that you have an extra } after your ct+=1; in your second while loop.
The correct code would be: Fiddle
i = 0;
var ranList = [];
while (i < 20) {
i++;
ranList.push(Math.floor(10 * Math.random()));
}
//count each number
document.write(ranList.sort().join("<br>"));
function ctnumber(array, elem) {
var ct = 0;
var j = 0;
while (j < array.length) {
j++;
if (array[j] == elem) {
ct += 1; // NOTE NO } HERE NOW
}
}
return ct;
}
alert(ctnumber(ranList, 5));
I also suggest a bit of a code cleanup:
var i = 0;
var ranList = [];
while (i < 20) {
i++;
ranList.push(Math.floor(10 * Math.random());
}
function countNumbers(list, elem) {
var count = 0;
// For loops are generally more readable for looping through existing lists
for (var i = 0; i < list.length; i++) {
if (list[i] == elem) {
count++;
}
}
return count;
}
alert(countNumber(ranList, 5));
Please note that console.log() is a much better debugging tool, it can be accessed by F12 in Firefox and Chrome/IE.

JS - Push functions into an array and name them

I'm trying to add functions into an array. These have to be named 'opdracht1' through to 'opdracht10'.
Though, I cannot figure out how to give it a name.
var opdrachtArray = [];
for (i = 0; i < 10; i++) {
opdrachtArray.push(function() {func(i); });
}
It adds the functions but as I said earlier I cannot find out how to add a name.
Also, am I later just able to define the functions and call them when I need them?
Name your functions by placing them on the window object:
for (i = 0; i < 10; i++) {
f = function() { func(i); }); // but see below
window['opdracht' + i] = f
opdrachtArray.push(f);
}
However you have a more basic problem. All your functions close over i and therefore func is always going to be called with the value of i after the loop finishes, in other words, 10. One solution is:
function make_func(i) {
return function() {
return func(i);
};
}
then
for (i = 0; i < 10; i++) {
f = make_func(i);
window['opdracht' + i] = f;
opdrachtArray.push(f);
}
Or
for (i = 0; i < 10; i++) {
(function(i) {
var f = function() { func(i); };
window['opdracht' + i] = f
opdrachtArray.push(f);
}(i));
}
or just use func.bind(null, i), which does approximately the same thing.
for (i = 0; i < 10; i++) {
f = func.bind(null, i);
window['opdracht' + i] = f;
opdrachtArray.push(f);
}
If you want each function to be assigned to a name, use dictionary, not array:
var opdrachtDict = {};
for (i = 0; i < 10; i++) {
opdrachtDict["opdracht"+i]=func.bind(null,i);
}
function func(i){
alert(i);
}
opdrachtDict["opdracht3"](); //ok, lets call it and see if you think its still a wrong answer ;)...
You could store the name and function in an object and push each object to the array:
var opdrachtArray = [];
for (i = 0; i < 10; i++) {
var name = 'opdracht' + (i+1);
opdrachtArray.push({name : name, callback : function() {func(i); }});
}
Well a function defined in the global scope just ends up being a property of self/window anyways. i.e.:
function mr_function(){return 5;}
self["mr_function"];
window["mr_function"];
Both (property of self / window) reference the function we defined. I guess you could name your function that way if you're careful. Or assign them to some other object's property if you'd rather not make them global.

Can't call a function through setTimeout

I'm trying to make a few functions to work one after the other with a waiting time of 1.5 seconds between them.
NOW, when i try doing so with the same Id (Inside the "NoteList(>here<)", like 1, 2, 3, or any other, it works;
for (var i = 0; i < 36; i++)
{
setTimeout(function () { OnClcRandom(NoteList[0]) }, i * 1000 + 1000);
}
BUT! when i try doing so with the var i, it doesn't work and gets the all of the functions in the page stuck. any idea why?
for (var i = 0; i < 36; i++)
{
setTimeout(function () { OnClcRandom(NoteList[i]) }, i * 1000 + 1000);
}
That would be because all of the functions refer to the same live i variable, not the value of the variable at the time you called setTimeout(). Which means by the time the timeouts actually run your function i will be 36.
Try this instead:
for (var i = 0; i < 36; i++) {
(function(x){
setTimeout(function () { OnClcRandom(NoteList[x]) }, i * 1000 + 1000);
)(i);
}
This executes an anonymous function on each iteration of the loop, with each execution getting its own x parameter for use in your original function.
Javascript doesn't create local scope for block. :)
And in your second example var i equal 36 (last value).
You need create local scope inside loop.
for (var i = 0; i < 36; i++) {
(function (i) {
setTimeout(.......);
}(i))
}
You also may fixed 'i' value assign it to function property:
for (var i = 0, f; i < 36; i++){
f = function _callback() { var i = _callback.i; .....};
f.i = i;
setTimeout(f, i * 1000);
}

Categories