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');
Related
my for loop should have 7 iterations but it tends to have different no if iterations some times its 5 some 4 some 3
this is my javascript code
var start = new Date().getTime();
var end = new Date().getTime();
function timeTaken(start1, end1) {
return ((end1 - start1) / 1000);
}
function appear() {
document.getElementById("shape").style.display = "block";
start = new Date().getTime();
}
function delay() {
setTimeout(appear, Math.random() * 7000);
}
// delay();
function gameStart() {
for (var i = 0; i < 8; i++) {
delay();
document.getElementById("shape").onclick = function() {
end = new Date().getTime();
document.getElementById("shape").style.display = "none";
document.getElementById("time-taken").innerHTML = timeTaken(start, end) + "s";
}
}
}
So as far as I understand you are looking for a way to call something x times with a timeout and it seems as you want to have some game logic with it.
This might overshoot by quite a bit....
The event handler has to be bound only once since its trigerd on click, so keep it out of the loop.
As for the loop you want can either use setInterval or Promises or recursion. I opted for recursion since it is quite easy in this case since you want to call a loop for x times. Now with JS running asynchronous you wont know when the recursion will end since it can be x deep so I included a Promise chain to return the results and give you the option to do something with the results.
// event handler for click
document.getElementById("shape").onclick = function () {
end = new Date().getTime();
document.getElementById("shape").style.display = "none";
// using innerText instead of inner HTML, read MDN if you want to know why
document.getElementById("time-taken").innerText = `${(end - start) / 1000}s`;
// pushing result to array for results
result.push((end - start) / 1000)
}
// declarations
let start, end;
let result = [];
function play(rounds) {
return new Promise(resolve => {
// checking if another roud should be played
if (rounds <= 0) {
// setting last round timer
console.log(`play time is up starting last round`)
setTimeout(()=>{
// hidding the element when done
document.getElementById("shape").style.display = "none";
// resolving for results
resolve(result)
}, Math.random() * 7000)
} else {
// statrting timeout if round should be played
setTimeout(() => {
// setting start when square will be displayed
start = new Date().getTime();
// showing html element
document.getElementById("shape").style.display = "block";
// starting the next round --> user therefore has Math.random()*7000 to click the element
// after the other timer runs out start will be reset
resolve(play(rounds - 1))
}, Math.random() * 7000)
}
})
}
// calling the play function 5 times
play(5).then(res => console.log(res))
If you declare variable with var, problem can occupay, because you use setTimeOut, you must use let.
let allows you to declare variables that are limited in scope to the
block, statement, or expression on which it is used. This is unlike
the var keyword, which defines a variable globally, or locally to an
entire function regardless of block scope.
Below is a simple code snippet which should console log out the value of i after 100ms on each loop.
for(var i = 1; i <= 5; i++) {
setTimeout(function() {
console.log('Value of i : ' + i);
},100);
}
The desired output of the above code is:
Value of i : 1
Value of i : 2
Value of i : 3
Value of i : 4
Value of i : 5
But the actual output is:
Value of i : 6
Value of i : 6
Value of i : 6
Value of i : 6
Value of i : 6
The above result is because, var defines variable globally, or locally to an entire function regardless of block scope.
With the introduction of let which defines the variable in block scope, the above code can be refactored as shown below
for(let i = 1; i <= 5; i++) {
setTimeout(function(){
console.log('Value of i : ' + i);
},100);
}
Output:
Value of i : 1
Value of i : 2
Value of i : 3
Value of i : 4
Value of i : 5
See more : https://medium.com/front-end-developers/es6-variable-scopes-in-loops-with-closure-9cde7a198744
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.
Got take-home assignment:
"You need to build a stub for fetch(url) function, which will fail n requests and starting from n+1 will fetch data successfully. Must be way to configure it passing number of requests to be failed (n, required) and optional parameter 'time to wait before resolve/reject'. Also must be the way to reset the request counter invoking fetch.reset(). Just as original fetch(), function should return Promise."
So, we need fetch-like function with a functionality mentioned above. Problem is with fetch.reset() method. Can't figure out how I can attach function to callback function.
So, no problem with all of these except for fetch.reset().
function outer (num, time) {
let count = 1;
return function fetchIt() {
return new Promise((resolve, reject) => {
if (count > num) {
count++;
setTimeout(()=>{resolve('ok');}, time * 1000) // here actual data will be returned/resolved
} else {
count++;
setTimeout(()=>{reject();}, time * 1000)
}
})
}
}
let newFetch = outer(2, 2);
newFetch().then(()=>console.log('ok'), ()=>console.log('not ok')); // 'not ok'
newFetch().then(()=>console.log('ok'), ()=>console.log('not ok')); // 'not ok'
newFetch().then(()=>console.log('ok'), ()=>console.log('not ok')); // 'ok'
Now, how can i make newFetch.reset() method to reset counter to 1?
Tried prototype - nope. I think problems are with accessing inner function from outer scope.
Plunk for this stuff:
http://plnkr.co/edit/FiXKyDJ1E2cv8LuUMxRM
Assign fetchIt to a variable before returning, then add the function on there:
function outer (num, time) {
...
let fetchIt = function fetchIt() {
...
}
fetchIt.reset = function reset() {
count = 1 // Or whatever you need to do
}
return fetchIt
}
I have the following code:
if (array.indexOf("undefined")===-1){
do something...
}
My initial arrays is this:
array=[,,,,,,];
It gets filled with values as the program goes on but i want the function to stop when there are no undefined spaces. The above syntax though is not correct. Anybody can tell me why.
Your code looks for the string "undefined". You want to look for the first index containing an undefined value. The best way is to use findIndex:
if(array.findIndex(x=>x===undefined) !== -1) {
//do something
}
for(var ind in array) {
if(array[ind] === undefined) {
doSomething();
}
}
Your check didn't work because you passed the string "undefined" instead the the value itself. Also, .indexOf() is designed to explicitly ignore holes in the array.
It seems wasteful to use iteration to detect holes in the array. Instead you could just track how many holes have been filled by using a counter, and execute your code when the counter matches the length.
Either way, the proper way to detect a hole at a particular index is to use the in operator.
Here's a class-based solution for reuse:
class SpacesArray extends Array {
constructor(n, callback) {
super(n);
this.callback = callback;
this.counter = 0;
}
fillHole(n, val) {
if (!(n in this)) {
this.counter++;
console.log("filling hole", n);
}
this[n] = val;
if (this.counter === this.length) {
this.callback();
}
}
}
// Usage
var sa = new SpacesArray(10, function() {
console.log("DONE! " + this);
});
// Emulate delayed, random filling of each hole.
for (let i = 0; i < sa.length; i++) {
const ms = Math.floor(Math.random() * 5000);
setTimeout(() => sa.fillHole(i, i), ms);
}
I have a for loop that kicks off hundreds of async functions. Once all functions are done I need to run one last function but I can't seem to wrap my head around it knowing when all functions are complete.
I've tried promises but as soon as any of the functions in the loop resolve then my promise function completes.
for(var i = 0; i < someArray.length; i ++){
// these can take up to two seconds and have hundreds in the array
asyncFunction(someArray[i];
}
How can I tell once every function has completed?
An increment
You can add a callback which increments:
for (var i = 0; i < len; i++) {
asycFunction(someArray[i]);
asycFunction.done = function () {
if (i == someArray.length - 1) {
// Done with all stuff
}
};
}
A recursive approach
This type of approach is more liked by some developers but (might) take longer to execute because it waits for one to finish, to run another.
var limit = someArray.length, i = 0;
function do(i) {
asyncFunction(someArray[i]);
asyncFunction.done = function () [
if (i++ == someArray[i]) {
// All done!
} else { do(i); }
}
}
do(i++);
Promises
Promises aren't well supported at the moment but you can use a library. It will add a little bulk to your page for sure though.
A nice solution
(function (f,i) {
do(i++,f)
}(function (f,i) {
asyncFunction(someArray[i]);
asyncFunction.done = function () {
if (i++ === someArray.length - 1) {
// Done
} else { f(i) }
};
}, 0)
Many libraries have .all resolver:
jQuery
q
bluebird
and many more - https://promisesaplus.com/implementations
You can use them or learn their source code.
Assuming the code to be the body of function foo() :
function foo() {
return Promise.all(someArray.map(function(item) {
//other stuff here
return asyncFunction(item, /* other params here */);
}));
}
Or, if there's no other stuff to do, and no other params to pass :
function foo() {
return Promise.all(someArray.map(asyncFunction));
}
You can check number of response.
For every response you can increase counter value and if counter value same as someArray.length then you can assume all Async functions are done and can start next step.