I have a function that when called will decrease by 1. It is called when a user reports something. I want to be able to store this and then when it hits 0, to execute an action.
function userReported() {
console.log('user report ' + add());
var add = (function () {
var counter = 10;
return function () {
counter -= 1;
return counter;
}
})();
}
Now the problem is I can return the counter so it logs down from 10. But the issue I have is that I can seem to add an if/else before returning counter as it does not store the variable.
I attempted the following but it doesn't work and I don't know how to return something > store it, and at the same time check its value. I also tried a while loop but failed too.
function userReported() {
var limit = add;
if ( limit <= 0 ) {
console.log('Link does not work!');
}
else {
console.log('user report ' + limit);
}
var add = (function () {
var counter = 10;
return function () {
counter -= 1;
return counter;
}
})();
}
How do I go about creating a value, increment/decrement said value, then when it reaches a number -> do something?
You would typically do this with a function that returns a function that captures the counter in a closure. This allows the returned function to maintain state over several calls.
For example:
function createUserReport(limit, cb) {
console.log('user report initiated' );
return function () {
if (limit > 0) {
console.log("Report filed, current count: ", limit)
limit--
} else if (limit == 0) {
limit--
cb() // call callback when done
}
// do something below zero?
}
}
// createUserReport takes a limit and a function to call when finished
// and returns a counter function
let report = createUserReport(10, () => console.log("Reached limit, running done callback"))
// each call to report decrements the limit:
for (let i = 0; i <= 10; i++){
report()
}
You can of course hard-code the callback functionality and limit number into the function itself rather than passing in arguments.
Ok, if you need to get a report based on an external limit, you could do something like that:
var limit = 10;
function remove() {
limit -= 1;
}
function userReport() {
if (limit <= 0) {
console.log("Link does not work!");
} else {
remove();
console.log(`User report: ${limit}`);
}
}
userReport();
If that's what you want, removing the remove function from userReport and taking the limit variable out will make things work
Related
I have a variable let second = 20 that i make 1 lower until it hits 0. When it hits 0 i want to stop running a part of my code but the variable second is always 20 when i use it because i make it lower in another scope. Sorry if my explenation is a bit unclear.
Here is the code:
votingEnd = document.querySelector(".imposters__voting");
imposters = document.querySelectorAll(".imposter");
let second = 20;
window.addEventListener("load", function () {
let myinterval;
myinterval = setInterval(function () {
second--;
if (second < 11) {
votingEnd.style.color = "red";
}
votingEnd.innerHTML = `Voting ends in: ${second}s`;
if (second == 0) {
clearInterval(myinterval);
votingEnd.innerHTML = `Voting has ended`;
}
}, 1000);
});
if (second > 0) {
//second is still 20 here because i lowered it in my function above. How can i solve this
for (let i = 0; i < imposters.length; i++) {
imposters[i].addEventListener("click", function () {
let count = 0;
while (count < imposters.length) {
imposters[count++].classList.remove("voted");
}
this.classList.add("voted");
});
}
}
You could put the if (second > 0) inside the click function that way it will check for the most recent value of second instead of just once on load like so
for(let i = 0; i < imposters.length; i++){
imposters[i].addEventListener("click", function () {
if (second > 0) {
let count = 0;
while (count < imposters.length) {
imposters[count++].classList.remove("voted");
}
this.classList.add("voted");
}
});
The problem has nothing to do with scope. It has to do with timing. That last part of your code only runs once, before the interval runs twenty times.
Here's the order of operations:
Initialize second to 20.
Bind the countdown function to window.onload. (This does not run yet)
Check if seconds is greater than 0, and it is because the intervals haven't run yet. This is the only time this code ever runs.
window.onload is triggered, and your countdown begins
one second later, seconds is now 19
19 seconds later seconds is not 0, and the interval is cleared.
So what you need to do is trigger your code in each iteration of the interval.
You want something closer to:
let second = 20;
window.addEventListener("load", function () {
const myinterval = setInterval(function () {
second--;
// other logic here...
if (second > 0) {
countdownTick(); // runs every second with the interval handler
}
if (second == 0) {
clearInterval(myinterval);
// cleanup
}
}, 1000);
});
function countdownTick() {
// Do the stuff you need to do each second here
}
Your setInterval runs every second. That does not mean that the rest of the code also runs every second. Which is why second is still 20 when the code gets to if (second > 0).
So you need to make sure that this part of your code runs every second as well. One solution would be to wrap that code inside a function which you call inside the interval, like this:
votingEnd = document.querySelector(".imposters__voting");
imposters = document.querySelectorAll(".imposter");
let second = 20;
window.addEventListener("load", function () {
let myinterval;
myinterval = setInterval(function () {
second--;
if (second < 11) {
votingEnd.style.color = "red";
}
votingEnd.innerHTML = `Voting ends in: ${second}s`;
if (second == 0) {
clearInterval(myinterval);
votingEnd.innerHTML = `Voting has ended`;
}
check();
}, 1000);
});
function check() {
if (second > 0) {
//second is still 20 here because i lowered it in my function above. How can i solve this
for (let i = 0; i < imposters.length; i++) {
imposters[i].addEventListener("click", function () {
let count = 0;
while (count < imposters.length) {
imposters[count++].classList.remove("voted");
}
this.classList.add("voted");
});
}
}
}
const _debounce = (n, func) => {
//code here
}
const originFun = () => {
console.log('hit')
}
const _call = () => _debounce(2, originFun)
_call() //The originFun not executes
_call() //hit
_call() //The originFun not executes
_call() //hit
I do not know how to implement it, even after a test.
_debounce should accept the sort argument, and the originFun function, and return its own function (or closure) that you can assign to _call. You can then call _call with it's own argument - the number.
function originFun(sort) {
console.log('hit,', sort);
}
function _debounce(sort, func) {
// Set a counter
let count = 1;
// Because closures retain the variables from the
// preceding lexical environment we can
// still use and update `count` when the function is returned
// This function (which accepts a number argument) is what's
// assigned to _call.
return function(n) {
// Call originFun with the number if the remainder of dividing
// sort by count is zero
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Remainder
if (count % sort === 0) func(n);
count++;
}
}
// _debounce returns a function that accepts a number
// argument. We assign that function to _call.
const _call = _debounce(2, originFun);
_call(1) //not execute
_call(2) //hit,2
_call(3) //not execute
_call(4) //hit,4
This implementation of debounce should help your use case. No global variable, and is implemented in pure javascript.
function _debounce(func, n){
let count = 0;
return (...args) => {
count++;
if (count % n === 0) {
count = 0;
func.apply(this, args);
}
};
}
const originFun = (sort) => {
console.log('hit,', sort)
}
const _call = _debounce((sort) => originFun(sort), 2);
_call(1) //not execute
_call(2) //hit,2
_call(3) //not execute
_call(4) //hit,4 */
_call(1) //not execute
_call(2) //hit,2
_call(3) //not execute
_call(4) //hit,4 */
To keep track of the number of calls you could use a global counter.
Additionally, you can pass arguments to your function using the spread syntax. That way you can have multiple arguments and all of them will be passed to the function in order.
let counter = 1;
const _debounce = (n, func, ...args) => {
if (counter === n) {
counter = 1;
func(...args);
} else {
counter += 1;
}
}
const originFun = (sort) => {
console.log('hit,', sort)
}
const _call = (sort) => _debounce(2, originFun, sort)
_call(1) //not execute
_call(2) //hit,2
_call(3) //not execute
_call(4) //hit,4
I'm struggling on how to increment a basic counter in javascript.
What do I want to achieve ?
I need a counter inside a foreach loop. The goal is to be able to count each time the //Write smthg is triggered.
Below is the updated version of the code I'm using. For the moment, it returns weird sequences of numbers. I guess it is resetted each time the recursive loop is triggered. I do not know how to correct it, suppose it's a basic javascript problem but as I'm learning through experimenting and on my own, I sometimes need to ask question to the community.
function walk(dir, counter = 0) {
fs.readdirSync(dir).forEach(file => {
let fullPath = path.join(dir, file);
if (fs.lstatSync(fullPath).isDirectory()) {
counter = walk(fullPath, counter);
walk(fullPath, counter);
console.log('dir');
} else {
let size = fs.statSync(fullPath).size; // Get size of file
listFiles.write(fullPath + " (" + size + ")\n"); // Write file path and size into copyList.xml
++counter;
console.log(counter);
}
});
return counter;
}
walk(copyFrom); // Starts function "walk"
Sequences obtained :
2,3,4,5,6,7,dir,5,6,8,9,10,11,12,13,dir,11
Here is the complete answer
function walk(dir) {
let n = 0;
function walk(dir) {
fs.readdirSync(dir).forEach(file => {
++n;
console.log(n);
let fullPath = path.join(dir, file);
if (fs.lstatSync(fullPath).isDirectory()) {
--n;
walk(fullPath);
console.log('dir');
} else {
let size = fs.statSync(fullPath).size; // Get size of file
listFiles.write(fullPath + " (" + size + ")\n"); // Write file path and size into copyList.xml
}
});
}
return walk(dir);
}
Use a helper. The function walk makes the lexical variable n and a function walk that shadows the called fucntion for the duration of the recursive calls. It may have the original content of walk and the outer function just returns the result of calling it as itself was called.
function walk(dir) {
let n = 0; //Counter variable
function walk(dir) {
dir.forEach(file => {
++n;
console.log(n);
if (true) {
//Recurse loop
} else {
//Write smthg
}
});
}
return walk(dir);
}
So, your issue is as follows:
Your counter will reset to 0 each time you recurse. So your numbers can go something like so: 0, 1, 0, 2, 0, 1, 2, 3, 3, ...etc. If you want to have an iterator counting total number of iterations, you'll need to pass your counter into the function and default it to 0 (for the first time walk is called), like so:
var files = ["dir1-file1", "dir1-file2", ["dir1-sub1-file1"], "dir1-file3", ["dir1-sub2-file1", ["dir1-sub2-subsub1-file1"]]];
function walk(dir, counter = 0) {
dir.forEach(file => {
if (Array.isArray(file)) {
// pass counter in to recursed function call
// set current function counter to the result of the recursed function calls
counter = walk(file, counter);
} else {
//Write smthg
++counter;
console.log(counter);
console.log(file);
}
});
// return the counter for parent
return counter;
}
walk(files);
I have a 'twice' function that return 2 of the argument passed into it. I also have another function 'runTwice' that counts the number of times it called the 'twice' function (the idea being that I want the 'twice' function to only run 'twice' no matter how often it is called via the 'runTwice' function). Can you please help?
Functions are given below:
var count = 1;
function twice(num){
return num*2;
}
function runTwice(func){
if (count<3){
count++;
return func;
} else {
return 'Function cannot run!';
}
}
var myFunc = runTwice(twice)
var output = [];
for (var i = 0; i < 3; i++){
output.push(myFunc(i));
}
console.log(output);
I would like the output to be [0, 2, 'Function cannot run!'].
I can make this work if I count the 'twice' function directly but I am looking to understand why this doesn't work as presented above.
Just for fun I'll make a generic expireAfter(invocable[, times[, message]]) function:
function expireAfter(invocable, times = 2, message = 'Function cannot run!') {
return function expires() {
if (times > 0) {
times--;
return invocable.apply(this, arguments);
}
return message;
}
}
function twice(n) {
return n * 2;
}
var myFunc = expireAfter(twice);
console.log(Array(3)
.fill()
.map((_, index) => myFunc(index))
);
The function runTwice should return another function that will decide whether to call the function func (using Function.prototype.apply) or to return a string message instead:
function twice(num){
return num * 2;
}
function runTwice(func){
var count = 0; // this will be trapped in a closure along with func
return function() { // this is the function that gets called
count++; // it increments its version of the count variable
if(count <= 2) // if count is less than 2
return func.apply(this, arguments); // then it calls the function func with whatever arguments passed into it and return the returned value of that call
return "Not available anymore!"; // otherwise (count > 2), then it returns a string
}
}
var myFunc = runTwice(twice);
for (var i = 0; i < 3; i++){
console.log(myFunc(i));
}
Even better:
You can pass in the number of times allowed as well:
function double(num) {
return num * 2;
}
function triple(num) {
return num * 3;
}
function run(func, times){
var count = 0; // this will be trapped in a closure along with func and times
return function() { // this is the function that gets called
count++; // it increments its version of the count variable
if(count <= times) // if count is less than times
return func.apply(this, arguments); // then it calls the function func with whatever arguments passed into it and return the returned value of that call
return "Not available anymore!"; // otherwise (count > times), then it returns a string
}
}
var double2times = run(double, 2); // double2times can only be called 2 times
var triple5times = run(triple, 5); // triple5times can only be called 5 times
for (var i = 0; i < 10; i++){
console.log("Double:", double2times(i));
console.log("Triple:", triple5times(i));
}
I am trying to create a page which needs to preform lots of loops. Using a while/for loops cause the page to hang until the loop completes and it is possible in this case that the loop could be running for hours. I have also tried using setTimeout, but that hits a recursion limit. How do I prevent the page from reaching a recursion limit?
var looper = {
characters: 'abcdefghijklmnopqrstuvwxyz',
current: [0],
target: '',
max: 25,
setHash: function(hash) {
this.target = hash;
this.max = this.characters.length;
},
getString: function() {
string = '';
for (letter in this.current) {
string += this.characters[this.current[letter]];
}
return string;
},
hash: function() {
return Sha1.hash(this.getString());
},
increment: function() {
this.current[0] += 1;
if (this.current[0] > this.max) {
if (this.current.length == 1) {
this.current = [0, 0];
} else {
this.current[1] += 1;
this.current[0] = 0;
}
}
if (this.current[1] > this.max) {
if (this.current.length == 2) {
this.current[2] == 0;
} else {
this.current[3] += 1;
this.current[2] = 0;
}
}
},
loop: function() {
if (this.hash() == this.target) {
alert(this.getString());
} else {
this.increment();
setTimeout(this.loop(), 1);
}
}
}
setInterval is the usual way, but you could also try web workers, which would be a more straightforward refactoring of your code than setInterval but would only work on HTML5 browsers.
http://dev.w3.org/html5/workers/
Your setTimeout is not doing what you think it's doing. Here's what it's doing:
It encounters this statement:
setTimeout(this.loop(), 1);
It evaluates the first argument to setTimeout, this.loop(). It calls loop right there; it does not wait for a millisecond as you likely expected.
It calls setTimeout like this:
setTimeout(undefined, 1);
In theory, anyway. In reality, the second step never completes; it recurses indefinitely. What you need to do is pass a reference to the function rather than the returned value of the function:
setTimeout(this.loop, 1);
However, then this will be window on the next loop, not looper. Bind it, instead:
setTimeout(this.loop.bind(this), 1);
setInterval might work. It calls a function every certain amount of milliseconds.
For Example
myInterval = setInterval(myFunction,5000);
That will call your function (myFunction) every 5 seconds.
why not have a loop checker using setInterval?
var loopWorking = false;
function looper(){
loopWorking = true;
//Do stuff
loopWorking = false;
}
function checkLooper()
{
if(loopWorking == false)
looper();
}
setInterval(checkLooper, 100); //every 100ms or lower. Can reduce down to 1ms
If you want to avoid recursion then don't call this.loop() from inside of this.loop(). Instead use window.setInterval() to call the loop repeatedly.
I had to hand-code continuation passing style in google-code prettify.
Basically I turned
for (var i = 0, n = arr.length; i < n; ++i) {
processItem(i);
}
done();
into
var i = 0, n = arr.length;
function work() {
var t0 = +new Date;
while (i < n) {
processItem(i);
++i;
if (new Date - t0 > 100) {
setTimeout(work, 250);
return;
}
}
done();
}
work();
which doesn't hit any recursion limit since there are no recursive function calls.