How to access a value outside a for loop - javascript

I'm trying to access the new value of a variable outside a for loop.
This is for a system where users get a point everytime they move a card to the right index (everything is stored in indexedDB). I've already tried to make a global variable, but it goes back to 0 the second I move it outside the loop. When I access it inside the loop I can see the new value. I need the new value for a new function that will store the different values in a descending order (bascially making a leaderboard).
How the code is now:
let danielCounter = 0;
for (let i = 1; i < doneTasks.result+1; i++) {
let getTasks = tasksStore.get(i);
getTasks.onerror = function() {
}
getTasks.onsuccess = function() {
if (getTasks.result.memberFullName == "Daniel") {
danielCounter++;
} else if (condition) {
//something to be executed;
}
}
}
console.log("d: " + danielCounter);
I would expect the console.log to show "d: 5", because that is what it would show if the console.log was inside the for loop, but instead it's showing "d: 0" which is the value it starts with.

My guess is that, since tasksStore.get(i) is an asynchronous call, the onsuccess function is being called after the console.log("d: " + danielCounter); synchronous instruction is called. Try waiting a couple of seconds to print the variable value, or putting the console.log inside onsuccess.
Check out this link for more information about synchronous and asynchronous processes.

The tasksStore.get(i) call is probably made asynchronously, so you will exit the loop before you have received all the results.
You could try awaiting the response in an async function, something like this
let danielCounter = 0;
getDanielCount();
async function getDanielCount(){
for (let i = 1; i < doneTasks.result+1; i++) {
let getTasks = await tasksStore.get(i);
getTasks.onerror = function() {
}
getTasks.onsuccess = function() {
if (getTasks.result.memberFullName == "Daniel") {
danielCounter++;
} else if (condition) {
//something to be executed;
}
}
}
console.log("d: " + danielCounter);
}

There are a few things that may cause an issue.
Javascript is single threaded and has a concurrency model based on an "event loop".
So, the order in which your code is executed is as follows.
1. let danielCounter = 0;
2.
3. for (let i = 1; i < doneTasks.result+1; i++) {
4. //taskStore.get is blocking method-- the method pushed into event event
5. // The control moves to next unblocked statement -- which is line 20!!
6. let getTasks = tasksStore.get(i);
7.
8. getTasks.onerror = function() {
9.
10. }
11.
12. getTasks.onsuccess = function() {
13. if (getTasks.result.memberFullName == "Daniel") {
14. danielCounter++;
15. } else if (condition) {
16. //something to be executed;
17. }
18. }
19.}
20. console.log("d: " + danielCounter);
Instead, try this code
async function getDanielCount() {
let danialCount = 0;
for( i=0; i< tasksStore.length; i++) {
let getTasks = await tasksStore.get(i);
if (getTasks.result.memberFullName == "Daniel") {
danielCounter++;
} else if (condition) {
//something to be executed;
}
}
return danialCount;
}
(async () => {
let count = await getDanielCount();
console.log('Daniel count', count); // This should display correct count
})();
The other thing I noticed is tasksStore.get(i) returns you an object 'getTasks' and your success and error functions are members of the newly created getTask object. Normally, you write a success and failure function separately and pass functions to the getTask method to receive a callback from getTask function.

Related

Callback function to console.log numbers in order

I am quite new to JavaScript and have a task for generating numbers in order of the functions being called with the use of callbacks.
The exercise should be done with a variable outside of the functions which should hold the returned values.
The starting code is as follows:
function asynchronousResponse(value, callback) {
var delay = Math.floor(Math.random() * 10) * 1000;
setTimeout(() => callback(value), delay);
}
function getAsyncNumber(number) {
/*
Add implementation of getAsyncNumber function in a way that numbers
appear on a console in order in which they have been called.
Use asynchronousResponse to generate this responses.
*/
}
getAsyncNumber(0);
getAsyncNumber(1);
getAsyncNumber(2);
getAsyncNumber(3);
getAsyncNumber(4);
I am not clear on how this is supposed to work and how to check if the previous numbers have alreay been called out.
The callback inside setTimeout is going to run in an async manner. Making use of the asynchronousResponse we can pass our own function as a callback. Notice the statement setTimeout(() => callback(value), delay);, this callback function will have access to our value and we can make use of that.
The implementation:
Now the idea is to keep storing the values as the functions are called. That way we have the definite order of our calls to getAsyncNumber. I think this refers to the variable outside the function which holds the returned values. We also keep a field for the status of the items. So we have an array of objects like {status,value}.
We update our array as our callbacks are called, but never log the resolved values. We only log something based on certain conditions. ONLY WHEN IT IS READY. Any resolved value will only be ready when everything before it is either already logged or in READY
state.
Whenever any callback is called and returns a value, we check whether everything before the item is done, if yes, then we log the resolved value. If everything before it is in ready state, we log them too and then log our resolved value.
There is an edge case, where some items later in order are ready but they do not get logged. So as a cleanup, we keep track of the number of resolved values too using a counter variable. If all our items have been resolved then we just log everything in the array that is still in READY state.
Here is a working code:
function asynchronousResponse(value, callback) {
var delay = Math.floor(Math.random() * 10) * 1000;
setTimeout(() => callback(value), delay);
}
const vals = [];
let resolved = 0;
function getAsyncNumber(number) {
vals[vals.length] = {status : "NOT_READY", number };
const func = () => {
resolved++;
let ready = 0;
let found = -1;
for (let i = 0 ; i < vals.length;i++) {
if (vals[i].number === number) {
found = i;
if (ready === i) {
vals[i] = {status : "DONE", number : vals[i].number}
for (let j= 0 ; j < i;j++) {
if (vals[j].status === "READY") {
vals[j] = {status : "DONE", number : vals[j].number}
console.log(vals[j].number);
}
}
console.log(number);
} else {
vals[i] = {status : "READY", number : vals[i].number}
}
} else if(found === -1) {
if (vals[i].status === "READY" || vals[i].status === "DONE" ) {
ready++;
}
}
}
if (resolved === vals.length) {
for (let i = 0 ;i < vals.length;i++) {
if (vals[i].status === "READY") {
console.log(vals[i].number);
}
}
}
}
asynchronousResponse(number,func);
}
getAsyncNumber('a');
getAsyncNumber('b');
getAsyncNumber(2);
getAsyncNumber(3);
getAsyncNumber(4);
getAsyncNumber('5');

How can I update the condition in a function-wrapped while with callback without using eval()?

I would like to define a function which will act as a while statement (with some addons, but for reasons of simplicity, I will show here a basic wrapper). So with conditions as the first parameter, and the callback to execute at each loop as the second one.
I came at first with this version:
const wrappedWhile = (conditions, callback) => {
let avoidInfinite = 0;
while (conditions) {
callback();
if (avoidInfinite >= 10) {
console.log('breaking while statement for avoiding infinite loop');
break;
}
avoidInfinite++;
}
};
let i = 0;
wrappedWhile(i < 5, () => {
console.log('log from callback: i =', i);
if (i >= 5) {
console.log('the loop continues whereas it should stop');
}
i++;
});
Logically, it is expected to stop when i >= 5. But the conditions parameter is a simple boolean in the wrappedWhile function, so it is always true as i was less than 5 at call.
Then, I came up with another version where conditions is evaluated at each iteration of the loop:
const wrappedWhile = (conditions, callback) => {
while (Function('return ' + conditions + ';')()) {
callback();
}
};
let i = 0;
wrappedWhile('i < 5', () => {
console.log('log from callback: i =', i);
i++;
});
But, if I am not wrong, Function is using eval() in order to work, and all of us once heard that the usage of eval() is not really safe towards code injection.
Now my question is simple: are there more secure alternatives to do what I want to achieve?
After some researches, I found a link which shows a way to evalify in a sandbox environment, but I don't know if it is good way or not.
You should pass a function as a condition and call it in the while loop
const wrappedWhile = (conditions, callback) => {
let i = 0;
while (conditions(i)) {
callback(i);
if (i >= 10) {
console.log('breaking while statement for avoiding infinite loop');
break;
}
i++;
}
};
wrappedWhile((i) => (i < 5), (iteration) => {
console.log('log from callback: i =', iteration);
});

chrome.storage.sync call not firing [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.

Wait for click event inside a for loop - similar to prompt()

This might not have the greatest title. I'm trying to understand call back functions, and I was wondering how replacing prompt() in the following code could be achieved, without losing the for loop?
for(i=0;i<4;i++){
let x = prompt("Input an integer");
// store input into an array
}
I've tried something like:
for(let i = 0; i<4; i++){
let x = document.getElementById("someId");
x.addEventListener("click", rcvInput(function(i){
if(i == 3){
x.removeEventListener("click", rcvInput)
}
}));
}
function rcvInput(callback){
//store input into an array
callback();
}
I know this can be done without the for loop, I'm more curious if callbacks could be able to pause the loop and wait for input?
Depending on what your end goal is, I'm pretty sure there's a better way to do it. But for the sake of doing that:
You can create a method that returns a promise that resolves when a click happens. Then you can use async/await to do what you need.
By using a Promise and awaiting on it, you can technically "pause" your for loop until something happens. In this case, a click.
Remember the method that encloses the for loop has to be async.
function getClick() {
return new Promise(acc => {
function handleClick() {
document.removeEventListener('click', handleClick);
acc();
}
document.addEventListener('click', handleClick);
});
}
async function main() {
for (let i=0;i<4;i++) {
console.log("waiting for a click", i);
await getClick();
console.log("click received", i);
}
console.log("done");
}
main();
Try it in this plunkr.
To acheieve:
for(var i=0;i<4;i++){
let x = prompt("Input an integer"); // WAIT FOR PROMPT
// ...
// LOOP CODE AFTER PROMPT
}
you can use recursion:
function promptLoop(count){
let x = prompt("Input an integer");
// ...
// LOOP CODE AFTER PROMPT
if (count > 0) promptLoop(count - 1)
}
and use it like so:
promptLoop(4);
Your second scenario is different, and can be adapted like so:
function loop(count, method) {
if (count > 0) method(() => loop(count - 1, method), count);
}
Your function would then take a next callback, like so:
function toBeLooped(next){
// do stuff
next() // continues loop
}
loop(3, toBeLooped);

js add event listener not working

I am trying to load interactivly some questins from questions' array (q) into my page and after student clicks at one of the 2 questions register the answer and change the question using js while loop. What is my mistake that it doesn't work?
var q = [
['NFR', 'Reusability'],
['NFR', 'Robustness'],
['FR', 'Reporting Requirements'],
['FR', 'Historical Data']
];
var correct = 0;
var incorrect = 0;
var total = q.length;
var i = 0;
document.getElementById('nick').innerText = name;
document.getElementById('question').innerText = q[0][1];
var nfr = document.getElementById('non-functional');
var fr = document.getElementById('functional');
function callback(ans) {
if (q[i][0] === ans) {
correct++;
} else {
incorrect++;
};
if (i < q.length - 1) {
i++;
document.getElementById('question').innerText = q[i][1];
} else {
location.href = "answer.html";
}
}
nfr.addEventListener('click', callback('NFR'));
fr.addEventListener('click', callback('FR'));
Your while loop is looping endlessly, because the only thing it does logically is set toNext to False, set some event listener callbacks and then evaluate toNext again, which will always be False. So i++ will never be called and i < q.length will always be True.
Get rid of the while loop. Write a separate function that evaluates the answer and updates your window with the next question. Use that function as callback for the click events.
In a callback function this will be set to the object calling the function, so you could write a function like this:
function callback() {
if (this.id == q[i][0]) {
window.correct++;
} else {
window.incorrect++;
}
i++;
set_question(i); // Logic to set the i-th question.
}
Edit
function callback(ans) {
// This function will return
// another function with the ans
// variable set, which in turn
// will be called as a callback
// when needed by the 'click' event.
return function () {
if (q[i][0] === ans) {
correct++;
} else {
incorrect++;
};
if (i < q.length - 1) {
i++;
document.getElementById('question').innerText = q[i][1];
} else {
location.href = "answer.html";
}
}
}
Event listeners are executed asynchronously and the code you write might assumes that the listeners can block loop execution.
To fix this, try removing the loop and move the logic of switching to the next question into the listener callback.

Categories