I'm trying to build a simple game in React and I need to make an animation where a set of blocks change their color to orange for 1 sec and go back to white. This operation needs to happen one by one.
Here is my game with an array of divs:
And let's say the array is [1,3,5,6]
I want to loop through that array and add a class with bg color to each div for a second and then removed it
I tried many things but the close I can get I that all the divs change their color at the same, not one by one
this is my code with a function that is starting by pressing "next lvl" and another async function
const displayBlocks = async (key) => {
let promise = new Promise((res,rej) => {
setTimeout(() => {
divArray.classList.remove('active');
res('color removed');
}, 500)
});
console.log(key);
ref.current.childNodes[key].classList.add('active');
let result = await promise;
return result;
}
const handleNewGame = () => {
blockArray = []
// generate array with random cells according to points
for (let i = 0;i< props.points;i++) {
let key = Math.floor(Math.random() * (props.board.length)) + 0;
displayBlocks(key).then((res) => {
console.log(res);
blockArray.push(key);
})
}
console.log(blockArray);
}
I tried many solutions with async or without but none of them worked. Can you tell me how can I stop the execution of the loop until the displayBlock functions are completed?
Note: Can't test it at the moment, might run into the same problem
Your displayBlocks method is not awaitable. Have you tried something like
const displayBlocks = async (key) => {
return new Promise((res,rej) => { // return with promise to make it awaitable
setTimeout(() => {
divArray.classList.remove('active');
res('color removed');
}, 500)
});
console.log(key);
ref.current.childNodes[key].classList.add('active');
let result = await promise;
return result;
}
const handleNewGame = () => {
blockArray = []
// generate array with random cells according to points
for (let i = 0;i< props.points;i++) {
let key = Math.floor(Math.random() * (props.board.length)) + 0;
var res = await displayBlocks(key); // await should halt the loop
console.log(res);
blockArray.push(key);
}
console.log(blockArray);
}
If you share the full working code in some online code editor I can fix it, but for now, I will share some pseudo code that you can convert into real code.
The idea is, if you want your divs change color 1 by 1, then add the class also one by one, keep track of the time in your calculation. See the pseudo code below,
function displayBlock (index) {
let duration = 1000
let waitTime = index * duration
let thisDiv = divArray[index]
// Now wait for waitTime amount of time before adding the OrangeClass
setTimeout(() => {
thisDiv.addClass('OrangeClass')
//Now from this point, wait for another 1 sec and remove the OrangeClass
setTimeout(() => {
thisDiv.removeCLass('OrangeClass')
}, duration)
}, waitTime)
}
If you can perfectly implement it, it should work as per your expectation. Just do some trial and error.
You can do it. Cheers :)
Related
im looking for a way to asynchronous iterate over an array and update a variable and in the end return this variable.
export const test = async(
...
): Promise<number> => {
let highestAmount = 0;
for (const entry of entries) {
const check = async () => {
let amount = 0
try {
newAmount = await getAmount(entry);
} catch (e) {
return;
}
if (newAmount > amount ) {
highestAmount = newAmount;
}
}
check()
}
return highestAmount;
}
In this current state i only get 0's back because the function doesnt wait for its finish.
Is there a way that the function only returns if all processes inside the for are finished ?
Lets say the getAmount(entry) function takes 1 second to finish then i have to wait around entries.length seconds. Im trying to find a way to execute this in 1 second so getAmount is called for every entry asynchronously => function returns highest number
Lets say the getAmount(entry) function takes 1 second to finish then i have to wait around entries.length seconds. I'm trying to find a way to execute this in 1 second
If you have five calls that take one second you can't execute and return a value from the function in one second.
Is there a way that the function only returns if all processes inside the for are finished?
Yes. This, however, is possible.
If you map over your array to produce an array of promises that you can await with Promise.all, you can then use Math.max to get the highest number from that array.
// Generate a random number
function rnd() {
return Math.floor(Math.random() * (100 - 0) + 0);
}
// Mock API call which returns the number passed into
// multiplied by an erratic method of
// creating a new random number
function getAmount(el) {
return new Promise(res => {
setTimeout(() => res((el - rnd()) + rnd()), 500);
});
}
// Create an array of promises, await them to resolve
// and then return the highest number
async function getHighest(entries) {
const promises = entries.map(el => getAmount(el));
const data = await Promise.all(promises);
console.log(data);
return Math.max(...data);
}
// Await the promise that `getData` returns
// and log the result
async function main(entries) {
console.log(await getHighest(entries));
}
const entries = [1, 2, 3, 4, 5];
main(entries);
There are a couple of things missing to make this wait:
Put an async in the parent function
Put an await in the check function call
For example:
export const test = async (
...
): Promise<number> => {
//...
await check();
};
There might also be ways to make these async calls run in parallel.
Problem
I am building algorithms simulator tool to simulate how algorithms work.
In BFS Algorithm I wanted to slow down the result display
So I used setTimeout function after each step to wait about 10ms before hitting the next step.
I used promise to be able to use async to easily wait the whole period and force the algorithm to stop till the wait function finishes
function implementation
function wait(time) {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, time);
})
}
BFS
while (queue.length) {
let currentId = queue[index++];
let tile = board.tileOf(currentId);
tile.visit();
if (currentId == targetId) {
return;
}
await wait(10);
for (let direction of DIRECTIONS) {
let childId = moveId(currentId, direction);
if (childId == undefined)
continue;
childId = parseInt(childId);
let child = board.tileOf(childId);
if (child.available()) {
child.visit(true);
queue.push(childId);
pervNode[childId] = currentId;
}
}
}
the problem is when I run the code it works fine but sometimes it takes very long time to display the solution more and more than just 10ms.
I am wondering why it's not accurate?
is this because of the approach that I am using?
and if there is a better solution what could it be?
Try the tool from here
i don't know the actual output but i think that could work
while (queue.length) {
let currentId = queue[index++];
let tile = board.tileOf(currentId);
tile.visit();
if (currentId == targetId) {
return;
}
let timer;
const loop = () => {
for (let direction of DIRECTIONS) {
let childId = moveId(currentId, direction);
if (childId == undefined) continue;
childId = parseInt(childId);
let child = board.tileOf(childId);
if (child.available()) {
child.visit(true);
queue.push(childId);
pervNode[childId] = currentId;
}
}
};
const listener = () => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
loop();
}, 100);
};
listener();
}
As JavaScript is a single-thread language, when you place a function call inside setTimeout(), this function gets in a queue and waits until other functions in a call stack (that were called before it) are finished and the call stack become empty. Time you specify as a second parameter to setTimeout() starts counting from the moment of placing the function in the queue, but not always the queue is empty
I have a certain promise chain in my code that looks like this:
myPromise()
.then(getStuffFromDb)
.then(manipulateResultSet)
.then(manipulateWithAsync)
.then(returnStuffToCaller)
Now, in my manipulateWithAsync I'm trying to enhance my result set by calling the DB again, but it's not working as I expected, since while debugging i figured out the control moves to the next function which is the returnStuffToCaller
here's an idea of what's into my manipulateWithAsync function:
function manipulateWithAsync(rs) {
return rs.map( async function whoCares(singleRecord) {
let smthUseful = await getMoreData(singleRecord.someField);
singleRecord.enhancedField = smthUseful;
return singleRecord;
})
}
I get the point of this behaviour: the map function does work as expected and the promise chain doesn't give a duck about it since it's not working with the awaits.
Is there a way to allow my returnStuffToCaller function to wait till the async function did his job?
I also use bluebird and i tried to use coo-routine, so if you thing that it's a good solution I'll post my bluebird coo-routine failing code :)
Thanks!
The problem is in using async/await with Array.map
This answer should help: https://stackoverflow.com/a/40140562/5783272
rs.map iterator jumps to the next element without waiting in each separate iteration.
You need something like asyncMap
You can use - https://github.com/caolan/async
or either implement yourself
async function asyncMap(array, cb) {
for (let index = 0; index < array.length; index++) {
return await cb(array[index], index, array);
}
}
*cb function must be async one
Wrap your map with Promise.all return the Promise then await for the results wherever you call the manipulateWithAsync.
// MOCKS FOR DEMO
// Test data used as input for manipulateWithAsync
const testData = [
{ recordNumber: 1 },
{ recordNumber: 2 },
{ recordNumber: 3 }
];
// Mock function which returns Promises which resolve after random delay ranging from 1 - 3 seconds
const getMoreData = () =>
new Promise(resolve => {
const calledAt = Date.now();
setTimeout(() => {
resolve({
usefulData: `Promise called at ${calledAt}`
});
}, Math.floor(Math.random() * 3000) + 1000);
});
// SOLUTION / ANSWER
const manipulateWithAsync = async rs =>
Promise.all(
rs.map(async singleRecord => {
const smthUseful = await getMoreData(singleRecord.someField);
// Instead of manipulating original data,
// which might cause some unwanted side effects going forward,
// instead return new objects
return { ...singleRecord, enhancedField: smthUseful };
})
);
await manipulateWithAsync(testData);
I want a store js object that manages a mongodb collection and behaves like that:
store.insert(thing); // called from a pubsub system that don't wait the insert to finish
store.get(); // returns a promise that resolves to the things in the collection
// even if called immediately after insert it must contain the last thing inserted
I implemented it manually like that:
let inserts = 0;
let afterInserts = [];
const checkInsertsFinished = () => {
if (inserts === 0) {
afterInserts.forEach(resolve => resolve());
afterInserts = [];
}
};
const decrementInserts = () => {
inserts -= 1;
checkInsertsFinished();
};
const insertsFinished = () =>
new Promise((resolve) => {
afterInserts.push(resolve);
checkInsertsFinished();
});
const insert = (thing) => {
inserts += 1;
db.collection('mycollection').insertOne(thing).then(decrementInserts);
};
const get = async () => {
await insertsFinished(); // if there are inserts happening, wait for them to finish
return db.collection('mycollection').find({}).toArray();
};
return { insert, get };
I suppose that there are more standard ways to accomplish this but I miss the vocabulary to find libs or built-in features... How would you do that?
Thanks for your advices.
JavaScript is single threaded, none of code you write can be run at the same time on multiple threads so you should be able to do it this way:
let inserting = Promise.resolve(),
startGetting={};
const insert = (thing) => {
startGetting={};//de-reference startGetting
inserting = db.collection('mycollection').insertOne(thing)
return inserting;
};
const get = () => {
const rec = () =>
inserting.then(
_ =>
new Promise(
(resolve,reject)=>{
//the next op is truely async (although I wonder why no promise),
// someone can insert while this is completing
const start=startGetting;
db.collection('mycollection').find({}).toArray(
(err,results)=>
err
? reject(err)
//inserting could have been set to tby the time
// this completed (in theory) so use startGetting reference equality
: startGetting === start
? resolve(results)//while getting nobody inserted
: resolve(rec())
)
})
);
return rec();
};
return { insert, get };
I am new to the world of node.js and Javascript and I have a loop that goes over an array of objects
at a certain condition I need to call a function that does asnyc work
and the loop to stop while the function isn't done
fucntion foo1(arr){
for(var i=0 ; arr.length>i ; i++){
if(i==8){//or any other condition
doAsyncStuff(hits[i])
}
}
}
function doAsyncStuff(item){
parser.parseURL(someurl,function(error,result){
item.someprop=result.someprop;
})
}
the problem is no matter what I do, I can't seem to make the function wait it end's before I have the result and doesn't update the item I need it to update.
I understand it's a common issue but none of the solution I found worked.
any help would be welcome.
Thanks!
Looping and doing async stuff is a little tricky in JS. You could use one of the libraries that #smnbbrv mentioned in his comment. But you could also do it yourself, which can help you understand how some of these libraries work.
function foo1(arr) {
next(arr, 0)
}
function doAsyncStuff(item, cb) {
parser.parseURL(someurl, function(error, result) {
item.someprop = result.someprop;
cb(result);
})
}
function next(arr, i) {
// Stop when we reach the end of the array.
if (i >= arr.length) {
return;
}
if (i == 8) { // or any condition
// Move to the next item only when the async work is done.
doAsyncStuff(arr[i], function() {
next(arr, i + 1)
})
} else {
next(arr, i + 1)
}
}
I would use Bluebird Promises and the reducer pattern.
var Promise = require('bluebird');
// iterates over the array 'arr' and calls fn
// Myfn repeatedly, optionally passing an initial.
// for the 1st iteration. For subsequent iterations,
// the value returned by prev invocation of Myfn is passed.
Promise.reduce(arr, Myfn, someInitialValue);
function Myfn(prev, item) {
//return a value or a promise.
}
see documentation of Reduce here: http://bluebirdjs.com/docs/api/promise.reduce.html
If I understand you correctly then you could use a Promise chain, serialised using reduce (a for loop would also work), something like this
function doAsyncStuff(item) {
return new Promise(resolve => {
const time = Math.ceil(Math.random() * 2000 + 1000);
window.setTimeout(() => {
item.someprop = time;
resolve();
}, time);
});
}
function foo1(arr) {
return arr.reduce(
(promise, item, index) => index % 2 === 0 ? promise.then(() => doAsyncStuff(item)) : promise,
Promise.resolve()
);
}
const hits = new Array(9).fill().map(() => ({}));
foo1(hits).then(() => {
console.log(hits);
});
There is also Promise.all, which you could probably use (though not sure how wonderful that would be, I'm not a frequent Promise user).
Update: Using Promise.all
function doAsyncStuff(item) {
return new Promise(resolve => {
const time = Math.ceil(Math.random() * 2000 + 1000);
window.setTimeout(() => {
item.someprop = time;
resolve();
}, time);
});
}
function foo1(arr) {
return Promise.all(
arr.map((item, index) => index % 2 === 0 && doAsyncStuff(item))
);
}
const hits = new Array(9).fill().map(() => ({}));
foo1(hits).then(() => {
console.log(hits);
});
I still haven't figured out the best way to format ES6, it always seems to end up with longish lines. (personal styling issue) :)