I have an array of objects containing a field duration that specifies the duration that the Object should stay visible.
Each Object of array has it's own duration.
So i want to make each object appear after another with the help of setTimout.
Something like this : (onClick)
const playScenes = () => {
const cpScenes = [...scenes];
for(let i; i < cpScenes.length;i++){
const timer = setTimeout(() => {
showScene(cpScenes[i]); //Show the current scene
}, cpScenes[i].duration); //Time changes automatically
}
clearTimeout(timer);
};
The way am thinking about this is that setTimeout should block execution of for loop and then run after specified time and then the loop continues..
Neither setTimeout nor setInterval block execution. Instead what you can do is wrapping it a promise like:
async function playscenes() {
const cpScenes = [...scenes];
for(let i; i < cpScenes.length;i++){
await (new Promise( (resolve) =>
const timer = setTimeout(() => {
showScene(cpScenes[i]);
clearTimeout(timer);
resolve();
}, cpScenes[i].duration);
})
}
Basically what this does, is for every single loop, the program will wait for the new Promise to resolve, which that happens when the timer has run out.
If you don't understand asynchronous code (async/await) and promises work, I'd highly recommend that you do your research first.
Although this code should not run inside an a React component since i assume that you change the state every time the setTimeout is triggered.
What you could do is:
imports ...
async function playscenes(showScene){...}
let scenesPlaying = false;
export function YourComponent(...){
...
if(!scenesPlaying) {
playscenes(showScene);
scenesPlaying = true;
}
...
}
I don't really see how it is related to react, anyway, you can use recursion instead of for-loop to make it work
function playScenes(scenes) {
if (scenes.length === 0) {
return;
}
const [scene, ...rest] = scenes;
setTimeout(() => {
showScene(scene);
playScenes(rest);
}, scene.duration);
}
Here's a simple for loop based solution:
function newComputerMessage() {
let total = 0;
for (let i = 0; i < cpScenes.length; i++) {
total += cpScenes[i].duration;
setTimeout(() => showScene(cpScenes[i]), total);
}
}
In case you want to see the working, here's a working example snippet with dummy values:
let cpScenes = [5000, 5000, 3000, 4000]
function test() {
let total = 0;
for (let i = 0; i < cpScenes.length; i++) {
total += cpScenes[i];
setTimeout(() => showScene('test'), total);
}
}
function showScene(num) {
console.log(num);
}
test()
Related
I am working on this react component. I want it to modify an array using a loop and reflect the changes in the UI after each iteration. I also added some code to halt the program so that each iteration will be displayed for a short time.
As for the UI, I have the numbers in the array itself followed by a button to start the loop. It seems that after pressing the button, the ui just freezes and the array is displayed but only after the last iteration of the loop. I want the onscreen array to change after every loop iteration. I tried using this.forceUpdate() but it did not change anything. I also tried using the spread (...) operator for changing the state but that did not change anything either. The relevant code and the GitHub are pasted below. The code can be found in src/ChangingArray.js in the project files. Thanks in advance.
import React, { Component } from 'react'
export default class ChangingArray extends Component {
constructor(props) {
super(props);
this.state = {
array: [1, 2, 3]
}
this.modifyArray = this.modifyArray.bind(this);
}
modifyArray = () => {
for (let i = 0; i < 15; i++) {
let newArray = [];
for (let j = 0; j < this.state.array.length; j++) {
newArray[j] = this.state.array[j];
}
newArray[i % newArray.length] = i;
this.setState({
array: [...newArray],
})
this.forceUpdate();
//Code to halt the program
const date = Date.now();
let currentDate = null;
do {
currentDate = Date.now();
} while (currentDate - date < 250);
}
}
render() {
return (
<div>
{this.state.array}
<button onClick={this.modifyArray}>modify</button>
</div>
)
}
}
https://github.com/AntonM-248/longestPalindromicSubstringVisualizer
By doing
do {
currentDate = Date.now();
} while (currentDate - date < 250);
you are synchronously blocking the thread, so your UI will obviously freeze:
I'm not sure I grasped what you are trying to achieve, but if you want to block the execution of a loop for a while before going to the next cycle, you need to implement an async iterator, let me show you how:
function makeAsyncArray(arr, delay) {
return {
[Symbol.asyncIterator]() {
let i = 0;
return {
next() {
const done = i === arr.length;
const value = done ? undefined : arr[i];
i++;
return new Promise((res) =>
setTimeout(() => res({ value, done }), delay)
);
},
return() {
// This will be reached if the consumer called 'break' or 'return' early in the loop.
return { done: true };
},
};
},
};
}
This function returns a special object called Async Iterator , this kind of iterators can be iterated by using this syntax:
for await (const el of arr) //do something
Every element returned by this Iterator is a Promise so it can be awaited inside an async function and that's what we need if we want to make delayed loops that wait for something before to proceed to go on with cyclle. In this example, I used just a delay with a setTimeout to yield and block the loop asynchronously for some time, but you can use this logic to resolve that Promise with any other async logic ( for example the result of a fetch request ).
This is a working React implementation of this logic:
const arr = makeAsyncArray([1, 2, 3, 4, 5,6,7,8,9,10], 1000);
export default function App() {
const [data, setData] = useState([]);
// Data array will be populated adding one element every 1000ms since we have initialized the async iterator to resolve after 1000ms
useEffect(() => {
(async () => {
for await (const el of arr) {
setData((d) => [...d, el]);
}
})();
}, []);
return <div>{data}</div>;
}
function makeAsyncArray(arr, delay) {
return {
[Symbol.asyncIterator]() {
let i = 0;
return {
next() {
const done = i === arr.length - 1;
const value = done ? undefined : arr[i];
i++;
return new Promise((res) =>
setTimeout(() => res({ value, done }), delay)
);
},
return() {
// This will be reached if the consumer called 'break' or 'return' early in the loop.
return { done: true };
},
};
},
};
}
The live demo: https://stackblitz.com/edit/react-yie8pg
Say I have the following generator that produces an infinite range:
const generateInfiniteRange = function* () {
for (let i = 0; true; i++) {
yield i;
}
};
const infiniteRange = generateInfiniteRange();
const infiniteRange$ = from(infiniteRange);
I can do something like this with RxJS:
const predicate = i => i < 10;
infiniteRange$.pipe(
takeWhile(predicate),
);
Now let's say that the predicate is asynchronous:
const predicate = async i => i < 10;
infiniteRange$.pipe(takeWhile(predicate)).subscribe(console.log);
How can I make this code work?
infiniteRange$.pipe(takeWhile(predicate));
I've tried using map as follows:
infiniteRange$.pipe(
map(async i => ({
i,
predicateResult: await predicate(i),
})),
takeWhile(({predicateResult}) => predicateResult),
pluck('i'),
);
but that just ends up mapping everything to a promise that always coerces to a truthy value, so everything passes through the takeWhile
I figured based on a previous question I've asked (for which originally I asked this question as an addendum in a comment of an answer, before deciding a question was more appropriate) that I could use concatMap, but that just generates an infinite emission before any of the internal observables hit the pipe.
If I understand this right, you can not achieve what you want.
The simple reason is that when, with from you create an Observable out of a generator, then you are creating a synchronous Observable, i.e. an Observable which emits all its values synchronously.
You can see clearly this looking at the fromIterable source code
function fromIterable<T>(iterable: Iterable<T>) {
return new Observable((subscriber: Subscriber<T>) => {
for (const value of iterable) {
subscriber.next(value);
if (subscriber.closed) {
return;
}
}
subscriber.complete();
});
}
As you can see, the for loop is exited only when the subscriber is closed. But in our case the subscriber will be closed asynchronously, i.e. we need Node to stop the execution of the loop since no instructions are left and pick the next callback, the one that runs the predicate. This will never happen since the for loop will never end.
So the summary is that you can not have an async predicate working with a synchronous infinite stream of values, which is what you create using a Generator.
By the way, in order for the code to compile, you need to use concatMap to transform the value notified by the source stream to the Object used by the predicate. So a code that compiles is this one
infiniteRange$.pipe(
tap(i => {
console.log(i)
}),
concatMap(async i => {
console.log('concatMap hit');
return {
i,
predicateResult: await predicate(i),
}
}),
takeWhile(({predicateResult}) => {
console.log('takeWhile hit');
return predicateResult
}),
pluck('i'),
).subscribe(console.log);
Running this snippet, you will see that you enter once in the concatMap input function (i.e. "concatMap hit" will be printed once) while you never enter the function passed to takeWhile (i.e. "takeWhile hit" will never be printed).
A SOLUTION WITH ASYNC GENERATORS
Actually, if you change the generator to be async, then using concatMap in the pipe we can reach the result you are looking for.
This is how the code would look like
const generateInfiniteRangeAsync = async function* () {
for (let i = 0; true; i++) {
await new Promise(resolve => setTimeout(resolve, 1000));
yield i;
}
};
const predicate = async i => i < 10;
const infiniteRangeAsync = generateInfiniteRangeAsync();
const infiniteRangeAsync$ = from(infiniteRangeAsync);
infiniteRangeAsync$.pipe(
concatMap(async i => {
console.log('concatMap hit');
return {
i,
predicateResult: await predicate(i),
}
}),
takeWhile(({predicateResult}) => {
console.log('takeWhile hit');
return predicateResult
}),
pluck('i'),
)
.subscribe(console.log);
A simple solution, using iter-ops, which can handle asynchronous predicates out of the box:
import {pipeAsync, stop} from 'iter-ops';
// your infinite generator:
const generateInfiniteRange = function * () {
for (let i = 0; true; i++) {
yield i;
}
};
// create our iterable:
const i = pipeAsync(generateInfiniteRange(), stop(async a => a >= 10));
// test:
(async function () {
for await(const a of i) {
console.log(a); //=> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
}
})();
P.S. I am the author of the library.
I'm trying to build one of my first web apps that would visualize sorting algorithms. I'm currently working on a bubble sort algorithm but I'm having trouble slowing the bubble sort loops execution down.
I tried using setTimeout() function but apparently, it is not that easy to use it on nested loops.
The piece of code below works well but if I add setTimeout within the nested loop then it only does the first iteration of the outer while loop. It is understandable as an entire while loop is executed instantly and isn't really aware that someone has desynchronized its' body execution.
I tried putting the outer loop in a setTimeout function but I couldn't find the way to retrieve the actual time that it is supposed to be delayed by as I tried using j value to calculate it but it is only available within the for loop.
Any idea on how to slow down execution of both loops in a relevant way?
bubbleSort(){
var len = this.numberOfElements;
var sorted = false;
var i = 0;
while(!sorted){
sorted = true;
for(let j = 0 ; j < len - i - 1; j++){
setTimeout( () => {
if (this.valuesAndPillarsObject[j].value > this.valuesAndPillarsObject[j + 1].value) {
//swap graphical representation of the value
this.swapPillars(this.valuesAndPillarsObject[j].pillar, this.valuesAndPillarsObject[j+1].pillar);
// swap values
var temp = this.valuesAndPillarsObject[j].value;
this.valuesAndPillarsObject[j].value = this.valuesAndPillarsObject[j+1].value;
this.valuesAndPillarsObject[j + 1].value = temp;
sorted = false;
}
}, j*40);
}
i++;
}
return this.valuesAndPillarsObject;
}
You don't want to just schedule a bunch of timeouts in a loop. They'll all finish after just one delay. You want to have each timeout start the next one. To keep using a loop to control the flow, you'll need to make use of async and await.
Wrong way
for (let i = 0; i < 5; ++i) {
setTimeout(() => { console.log(`step ${i}`) }, 1000);
}
console.log('finished');
Right way
const delay = time_ms => new Promise(resolve => setTimeout(resolve, time_ms));
const main = async () => {
for (let i = 0; i < 5; ++i) {
await delay(1000);
console.log(`step ${i}`);
}
}
main().then(() => console.log('finished'));
The for loop will finish before the first setTimeout callback is called !
even if the timeout is 1ms or 0ms.
read some about the EventLoop to understand how things works in JavaScript.
UPDATE: the simplest solution is using delay inside the for loop.
just like how Wyck did.
but here i will keep the other solutions
You can use function and call it again to demonstrate a loop instead of normal for loop.
// change the code as your needs
const sleep = (t) => ({ then: (r) => setTimeout(r, t) })
const len =10
// change slowFor for your needs
const slowFor= async (i,j)=>{
// do what you want here
await sleep(5000)
if(j == len) return ;
console.log("after sleeping call next loop");
slowFor(i,j+1)
}
slowFor(0,0)
be aware of maximum call stack when running on big arrays.
or you can convert the bubbleSort function to a generator function so you will be able to stop it whenever and as much time you want.
change the code to fit your need
function* bubble() {
for(let i = 1 ; i<10 ; i++ ){
for(let j=0 ; j<i ; j++, yield){
console.log("hellow from for");
}
}
}
const genBubble = bubble()
genBubble.next()
setInterval(() => {
genBubble.next()
}, 5000);
This question already has answers here:
How do I add a delay in a JavaScript loop?
(32 answers)
Closed 2 years ago.
I am new to javascript so sorry if I am misunderstanding how the language does some stuff,
I am building a sorting algorithms visualizer which orders blocks by their hue value (using chroma-js library) :
Each item in screenObject.items is a Color object
//color objects are what I am sorting
class Color {
constructor(div, color, value) {
//this div on the html page
this.div = div;
this.color = color;
//hue value of the color
this.value = value;
}
update(color, value) {
this.color = color;
this.value = value;
this.div.style.backgroundColor = color;
}
}
class ScreenObject {
constructor() {
//this is an array of color objects
this.items = [];
}
bubbleSort() {
let solved = false;
while (!solved) {
let swaps = 0;
this.items.forEach((item, index) => {
if (index > 0) {
swaps += compare(this.items[index - 1], item);
}
});
if (swaps === 0) {
solved = true;
}
}
}
}
function compare(color1, color2) {
if (color1.value > color2.value) {
swap(color1, color2);
return 1;
} else {
return 0;
}
}
function swap(color1, color2) {
colorStore = color1.color;
valueStore = color1.value;
color1.update(color2.color, color2.value);
color2.update(colorStore, valueStore);
}
The issue I have is that this colors only update after the program is completed, and if I add an setIterval, or setTimeout I have only been able to make the colors update after each pass, instead of after each comparison/swap (I want to add special styling when the colors are being compared):
bubbleSort() {
let solved = false;
while (!solved) {
let swaps = 0;
setInterval(() => {
this.items.forEach((item, index) => {
if (index > 0) {
swaps += compare(this.items[index - 1], item);
}
});
}, 50);
if (swaps === 0) {
solved = true;
}
}
}
I want to be able to see the colours update after every single comparison for example swap(1, 2) the user sees 1 get 2's color and 2 get 1's color.
Thanks in advance!
I'm going to assume you're doing this on a browser. You need to yield back to the event loop in order for other things to happen, such as repainting the page. Probably the simplest thing is to make your bubbleSort an async method and have it await something, such as a timer callback:
async bubbleSort() { // <=== Note the `async`
let solved = false;
while (!solved) {
let swaps = 0;
// *** No `setInterval`
for (const [index, item] of this.items.entries()) {
if (index > 0) {
const result = compare(this.items[index - 1], item);
if (result) { // *** Did it do something?
swaps += result;
await delay(0); // *** Wait for it to be redrawn
}
}
});
if (swaps === 0) {
solved = true;
}
}
}
...where delay might be:
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
When you call bubbleSort, it will return a promise almost immediately, and continue its logic asynchronously until it's done, then settle the promise.
Note that async functions were added in ES2018. The're well-supported by modern browsers, but you may need a tool like Babel to transpile for older ones.
If you want to be even more precise, you could use requestAnimationFrame rather than setTimeout with a zero-length timeout. Here's a somewhat counter-intuitive function for that:
const nextFrame = (cb = () => {}) => new Promise(resolve => {
requestAnimationFrame(() => {
cb();
resolve();
});
});
...which you'd use in place of delay:
await nextFrame();
(In your case, you don't need to pass any callback, the default do-nothing callback is sufficient.)
I explain the reason for the odd design of that function in this tweet (which in turn is asking whether it really needs to be designed that oddly).
Another approach is to invert your logic a bit and use a generator function that generates each swap. Then you can drive that generator in a loop. That's the approach I used when answering this other question about visualizing buble sort.
Currently, I have a function which is called to do a large task using many setTimeout's in order to not block page interaction.
Example code follows
function runAsync(maxMs, throttle, func){
var begin = window.performance.now();
var cont = func(throttle);
var end = window.performance.now();
if (cont){
var newThrottle = Math.ceil((throttle/(end-begin))*maxMs);
setTimeout(function(){
runAsync(maxMs, newThrottle, func);
}, 0);
}
}
a = 0;
function makeHuge(addNum){
var target = 1000000000;
for (var x = 0; x < Math.min(addNum, target-a); x++){
a ++;
}
$('#result').text(a);
return a < target;
}
$('#run').on('click', function(){
a = 0;
runAsync(16, 100, makeHuge);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span id="result"></span><br/>
<button id="run">Run</button>
As you can see above, setTimeout is used to do a long-running task without blocking user interaction. The called function, func, must return a boolean which says whether or not to continue doing the action. The throttle value is used to keep the execution time of func below maxMs, which, if set to 16, represents something like 60fps.
However, using this setup, I cannot figure out how to incorporate the new Promises to let me know when the runAsync task is finished, or if it failed.
I'll give two solutions, as there's a trade-off between speed and being idiomatic in your case.
Best performing solution
setTimeout has terrible error-handling characteristics, in that errors always end in web console, so use try/catch around everything to pass errors to reject manually. I also had to refactor a bit:
function runAsync(maxMs, throttle, func){
return new Promise((resolve, reject) => {
setTimeout(function again(){
try {
var begin = window.performance.now();
if (!func(throttle)) return resolve();
var end = window.performance.now();
throttle = Math.ceil((throttle/(end-begin))*maxMs);
setTimeout(again, 0);
} catch (e) {
reject(e);
}
}, 0);
});
}
a = 0;
function makeHuge(addNum){
var target = 1000000000;
for (var x = 0; x < Math.min(addNum, target-a); x++){
a ++;
}
result.innerHTML = a;
return a < target;
}
run.onclick = function(){
a = 0;
runAsync(16, 100, makeHuge)
.then(() => result.innerHTML = "Done!")
.catch(e => console.error(e));
};
<span id="result"></span><br/>
<button id="run">Run</button>
Quite ugly, with a Promise constructor and try/catch muck, but there's a clean alternative.
Most idiomatic solution
With a promise-returning wait helper to tame setTimeout, use promises throughout:
var wait = ms => new Promise(resolve => setTimeout(resolve, ms));
function runAsync(maxMs, throttle, func){
var begin = window.performance.now();
var cont = func(throttle);
var end = window.performance.now();
return (!cont)? Promise.resolve() : wait(0).then(() => {
var newThrottle = Math.ceil((throttle/(end-begin))*maxMs);
return runAsync(maxMs, newThrottle, func);
});
}
a = 0;
function makeHuge(addNum){
var target = 1000000000;
for (var x = 0; x < Math.min(addNum, target-a); x++){
a ++;
}
result.innerHTML = a;
return a < target;
}
run.onclick = function(){
a = 0;
runAsync(16, 100, makeHuge)
.then(() => result.innerHTML = "Done!")
.catch(e => console.error(e));
};
<span id="result"></span><br/>
<button id="run">Run</button>
Easy to read, and structured like your original code. Normally, this would be the clear winner.
However, be aware that there's a potential performance problem here if this is to run for a long time or an open-ended amount of time. That's because it builds up a long resolve chain.
In other words, as long there's more to do, every time wait resolves, it's with a new promise from a subsequent call to wait, and even though each promise gets resolved almost immediately, they all get fulfilled together with the same value (undefined in this case) only at the very end! The longer things run, the bigger the one-time jank may be when the last promise finally resolves with undefined.
While browsers could optimize this away, none do today AFAIK. See long discussion here.