Javascript Closure is not working? - javascript

I know this seems silly. But just now I tried to test the closure in JS.
var funcList = [];
for(let i = 0; i < 3; ++i) {
funcList.push(() => { console.log(i); });
}
console.log('testing for reference');
funcList.forEach(func => func());
funcList.length = 0;
for(let i = 0; i < 3; ++i) {
funcList.push(
((i) => {
return () => { console.log(i); }
})()
);
}
console.log('testing for no reference');
funcList.forEach(func => func());
But unluckily, I encountered issues as follows:
The expected output should be (at least I thought ~~, highly possible fact is I'm wrong):
testing for reference
3
3
3
testing for no reference
0
1
2
But it gave me
testing for reference
0
1
2
testing for no reference
undefined
undefined
undefined
What is happening out here? Can anyone shed the light upon me?
Thank you so much for any possible helpful advice.
UPDATE 2018-3-24 Thanks for #Patrick Evans It's working as follows now:
var funcList = [];
for(var i = 0; i < 3; ++i) { // using var instead of let
funcList.push(() => { console.log(i); });
}
console.log('testing for reference');
funcList.forEach(func => func());
funcList.length = 0;
for(var i = 0; i < 3; ++i) {
funcList.push(
((i) => {
return () => { console.log(i); }
})(i) // pass in the `i`
);
}
console.log('testing for no reference');
funcList.forEach(func => func());
Updated 2018-03-30
A real closure should be
var funcList = [];
(function () {
let i = 0;
while (i++ < 3) {
funcList.push(() => { console.log(i); });
}
})();
console.log('testing for reference');
funcList.forEach(func => func());

You forgot to pass i's value to the function, which is expecting a parameter. Since parameter doesn't have a value it logs undefined. Try this:
funcList.push(
((i) => {
return () => { console.log(i); }
})(i) //Pass i's value as param
);

Related

Not being able to reference a variable from outside the loop

Iam not able to reference a variable from outside the loop in the following function. I want to use the value inside the function itself but the scope doesnt seem to allow it.
function coinOnTheTable(m, k, board) {
for (var i = 0; i < board.length; i++) {
for (var j = 0; j < m; j++) {
if (board[i][j] === "*") {
const a = `${i}${j}`;
return a;
}
}
}
}
Works, While
function coinOnTheTable(m, k, board) {
for (var i = 0; i < board.length; i++) {
for (var j = 0; j < m; j++) {
if (board[i][j] === "*") {
const a = `${i}${j}`;
}
}
}
return a;
}
gives an error
return a;
^
ReferenceError: a is not defined
That's because a variable defined as const or let has "block scope". That means it is only visible inside the nearest set of curly braces. So:
function foo() {
const foo = 'foo-string';
if(1) {
const bar = 'bar-string';
console.log(bar); // This is fine
}
console.log(foo); // This is also fine
console.log(bar); // Error!
}
To solve your problem, you need to define your variable outside of the block defined by the nested for loop. So:
function coinOnTheTable(m, k, board) {
let a;
for(var i = 0; i < board.length; i++){
for(var j = 0; j < m; j++){
if(board[i][j] === "*"){
a = `${i}${j}`;
}
}
}
return a;
}
Note, the reason it changes from const to let is because const variables cannot be reassigned, but since you want to declare it as undefined (at the start of the function, then assign a string to it (inside the loop), you need to be able to reassign it.
make a function scope:
function coinOnTheTable(m, k, board) {
let a;
for(var i=0;i<board.length;i++){
for(var j=0;j<m;j++){
if(board[i][j]==="*"){
const a=`${i}${j}`;
}
}
}
return a;
}

Async Behavior in Javascript

The following function -
a = [1,2,3];
function f() {
for(let i=0; i < a.length; i++){
setTimeout(() => console.log(i),3000)
}
}
gives me output - 0,1,2 which is as expected.
However, the following function -
function f() {
for(var i=0; i < a.length; i++){
setTimeout((function(el){console.log(el)})(i),3000)
}
}
gives the following output -
0 and throws following error
Uncaught TypeError: Cannot read property '__wrapper_2__' of undefined
However, in the last function if I remove setTimeout, I am getting expected behavior i.e. -
function f() {
for(let i=0; i < a.length; i++){ (function(i){console.log(i)})(i);
}
}
I am getting the expected output - 0,1,2
So, Why setTimeout is giving me error with when defining new function, but not with arrow functions ?
You're attempting to call the function in the argument list. It doesn't work for the same reason this doesn't:
function f() {
for (let i = 0; i < a.length; i++) {
setTimeout(console.log(i), 3000);
}
}
f();
The first argument of setTimeout is an uncalled function, like:
setTimeout(() => console.log(i), 1000);
or
const method = () => console.log(i);
setTimeout(method, 1000)
Edit: Also,
function f() {
for (var i = 0; i < a.length; i++) {
setTimeout(() => {
(function (el) {
console.log(el);
})(i);
}, 3000);
}
}
let a = [1, 2, 3];
f();
Will return 3, 3, 3 because after 3000ms the given function will be called with the global variable i as a parameter, and at that point i === 3;
However, in the following example, the value of i at the time is saved in the function. I believe that has something to do with closures, maybe someone else can follow up with a better explanation of that.
function f() {
for (var i = 0; i < a.length; i++) {
setTimeout(() => {
console.log(i);
}, 3000);
}
}
let a = [1, 2, 3];
f();

change the number of the iterator in a for loop

I want to conditionally break out of a loop like this..
for(let i = 0; i < 3; i++) {
exampleFunction().then(result => {
res = 0 ? i = 3 : null
})
}
I want exampleFunction to run at least 3 times unless it gets the desired result, in which case I want it to stop running.
exampleFunction runs asynchronously. The only way to get it working is using async/await.
const iterateWithExampleFunction = async () => {
for (let i = 0; i < 3; i++) {
console.log('before', i)
await exampleFunction().then(result => {
i = result === 0 ? 3: i;
});
console.log('after', i)
}
};
const exampleFunction = async () => {
return 0;
}
iterateWithExampleFunction();
You can have a count on the outer scope and then do the async call.
let count = 0;
function executeCall() {
exampleFunction().then(result => {
// do something with the result
if (result !== 0 && count !== 3) {
count += 1;
executeCall();
}
});
}
Just await the result, than break:
(async function() {
for(let i = 0; i < 3; i++) {
const result = await exampleFunction();
if(result === 0) break;
}
})();
Hopefully this gives you some ideas
async function poll(f, threshold = 3) {
if (!threshold) {
throw new Error("Value not found within configured amount of retries");
}
const result = await f();
if (result >= 0.5) {
return result;
} else {
return await poll(f, threshold - 1);
}
}
async function task() {
return Math.random();
}
poll(task).then(console.log).catch(console.error);

How to concatenate operators in javascript?

I am writing next_previous() function for pagination purpose, I have for loop which is moving from 0 to the given length. I want to use the same loop for two cases from 0 to 10 and from 10 to 0.
for (var i = 0; i < 10; i++) {
}
for (var i = 10; i > 0; i--) {
}
to use both cases in one loop I am doing something like this but not working
var i = 0; a = '', b = '';
if(somevar === true){
i = 0 , a = '++', var b = '<';
}else{
i = 10 , a = '--', var b = '>';
}
for (i; i +b+ 0; i+a) {
}
now problem is javascript not allowing concatenation this way, how can I achieve this?
See screenshot
Try this approach which uses for the logic part ( increment and condition ) functions.
ES6
let i = 0;
let a;
let b;
let count = 0;
let somevar = true;
if(somevar) {
i = 0;
count = 10;
a = () => i++;
b = () => i < count;
} else {
i = 10;
count = 0;
a = () => i--;
b = () => i > count;
}
for (; b(); a()) {
console.log(i);
}
ES5
var i = 0;
var a;
var b;
var count = 0;
var somevar = true;
if(somevar) {
i = 0;
count = 10;
a = function() { i++; };
b = function() { return i < count; };
} else {
i = 10;
count = 0;
a = function() { i--; };
b = function() { return i > count; };
}
for (; b(); a()) {
console.log(i);
}
It seems like you are looking for an eval solution, but this really is not how one would approach this problem. Rather go for a functional design:
function forward(cb) {
for (var i = 0; i < 10; i++) cb(i);
}
function backward(cb) {
for (var i = 10; i > 0; i--) cb(i);
}
const loop = somevar ? forward : backward;
loop(i => {
…
});
This answer is a less-lazy version of #BiswajitPanday's answer. I would have edited his answer instead, but I realized that it's been a week since this question was posted, so an edit on his answer wouldn't be as fruitful since OP wouldn't be notified
Here is an eval solution:
function loopFoo(backwardsDirection, callback) {
if(backwardsDirection === true) {
var i = 10, b = '>', c = 0, a = '--';
} else {
var i = 0, b = '<', c = 10, a = '++';
}
for (i; eval("i" + b + c); eval("i" + a)) {
callback(i);
}
}
console.log("Forwards:");
loopFoo(false, console.log);
console.log("Backwards:");
loopFoo(true, console.log);
console.log("Forwards:");
loopFoo(false, console.log);
That function (eval) is generally frowned upon though, I would go with #Bergi's solution, if not that, #Suren's, and if not his either, my solution.
Just use eval(i+b+10) instead of i+b+10

How to make for loop log line by line with 1sec delay in JS

My code will log all 10 lines in the order that I want (descending triangle), but I need it to delay 1 second before logging each successive line. I tried putting a setTimeout before the for loop, but that just caused a 1 second delay before printing all 10 lines concurrently.
function minusTen(num) {
var arr = '';
for (var i = num; i > 0; i--) {
arr += '*';
}
var newArr = arr.split('');
for (var j = num; j > 0; j--) {
newArr.pop();
console.log(newArr.join(' '));
}
}
minusTen(10);
I can use jQuery but I'd like to avoid having to implement Bootstrap if possible.
Thank you!
you can use setTimeout for it but then you will have to keep setTimeout inside the for loop. you can also use setInterval here and clear the interval if num becomes 0. something like this:
function minusTen(num) {
var arr = '';
for (var i = num; i > 0; i--) {
arr += '*';
}
var newArr = arr.split('');
var interval = setInterval(function(){
newArr.pop();
console.log(newArr.join(' '));
num--;
if(!num)
clearInterval(interval);
}, 1000)
}
minusTen(10);
You can use a function. Check the .length of newArr, if greater than 0, call function again
function minusTen(num) {
var arr = '';
for (var i = num; i > 0; i--) {
arr += '*';
}
var newArr = arr.split('');
function fn() {
if (newArr.length)
setTimeout(function() {
console.log(newArr.join(" "));
newArr.pop();
fn()
}, 1000)
else
console.log("done, newArr.length:", newArr.length);
}
fn()
}
minusTen(10);
function minusTen(num) {
var arr = '';
for (var i = num; i > 0; i--) {
arr += '*';
}
var newArr = arr.split('');
function printLine(counter){
var k = counter;
window.setTimeout(function(){
console.log(newArr.join(' '));
newArr.pop();
}, k*1000);
console.log(k);
}
for (var j = num; j > 0; j--) {
printLine(j);
}
}
minusTen(10);
It's straightforward with async/await. delay holds the execution of the code for a specified amount of time.
async function minusTen(num) {
var arr = '';
for (var i = num; i > 0; i--) {
arr += '*';
}
var newArr = arr.split('');
for (var j = num; j > 0; j--) {
newArr.pop();
await delay(1000)
console.log(newArr.join(' '));
}
}
function delay(time) {
return new Promise((resolve) => setTimeout(resolve, time))
}
minusTen(10);
You can use setTimeout with an offset. Just add whatever parameters you need to the log function.
function log(offSet) {
setTimeout(() => {
const str = new Array(offSet + 1).join('*');
console.log(str);
}, 1000 * offSet);
}
for(let i = 1; i < 11; i ++) {
log(i);
}
https://jsfiddle.net/ycreaL9w/
If you wanted to reduce your code footprint a little you could do something like this:
const printStars = (n) => {
// Make sure we don't try to print zero stars
if (n > 0) {
// Fill an array with n stars
const arr = Array(n).fill('*').join(' ');
console.log(arr);
// After a second, reduce n by 1 and call
// the function again
setTimeout(() => printStars(--n), 1000);
}
}
printStars(10);
DEMO
It's worth pointing out here, however, that IE/Opera don't support Array.fill.
If you need to support them use an for/loop like in your example. Here I've separated out that code into its own function.
const getStars = (n) => {
let arr = [];
for (let i = 0; i < n; i++) {
arr.push('*');
}
return arr.join(' ');
}
const printStars = (n) => {
if (n > 0) {
console.log(getStars(n));
setTimeout(() => printStars(--n), 1000);
}
}
DEMO 2

Categories