I have an async function that has a loop that I need to be able to pause or unpause it. This is what I have so far.
I use a flag to pause the flow:
let flag = true;
function flag_func() {
flag = !flag;
}
$(document).ready(function () {
function sleep(ms) {
while (!flag) {
//...waiting.. but infinite loop
}
return new Promise(resolve => setTimeout(resolve, ms));
}
async function show_simulation(data) {
document.getElementById("solve-button").outerHTML = "<button type=\"button\" id='pause-button' onclick='flag_func()' class=\"btn btn-primary btn-lg\">Pause</button>";
//simulation
if (data.length === 0) {
console.log('stuff')
} else {
let i;
for (i = 0; i < data.length; i++) {
await sleep(40);
// do stuff
}
}
}
});
The problem is that is being paused, but due the while block the flow, I can't unpause the for loop.
Any idea about how I can solve this?
It might be a nice use case for async iterables. It involves a bit of boilerplate to create your async list, but then the code is much nicer. Basically you would have:
import AsyncList from './async-list.js'
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
async function f(data) {
const list = new AsyncList(data);
document.getElementById("btn-toggle").addEventListener("click", function () {
if (list.paused) {
this.textContent = "Pause";
list.resume();
} else {
this.textContent = "Resume";
list.pause()
}
})
for await (let item of list) {
console.log(item)
await sleep(1000);
}
console.log("end of loop")
}
f([10, "hello", 1029, 90, 80, 209, 44])
A possible implementation of AsyncList could be:
export default class AsyncList {
constructor(array) {
// shallow copy
this._array = array.slice();
this._index = 0;
this._length = this._array.length;
this.paused = false;
this._resume = () => {}; // noop, in case `resume` is called before `pause`
}
[Symbol.asyncIterator]() {
return this;
}
pause() {
this.paused = true;
}
resume() {
this.paused = false;
this._resume();
}
next() {
if (this._index < this._length) {
const value = this._array[this._index++];
if (this.paused) {
return new Promise(r => this._resume = r.bind(null, { value }))
}
return Promise.resolve({ value })
} else {
return Promise.resolve({ done: true });
}
}
}
Just to give to you the idea, you could also encapsulate the private properties, and check more scenarios (here I assume data is an array, for example, not just an iterable).
I'd replace:
let i;
for (i = 0; i < data.length; i++) {
await sleep(40);
// do stuff
}
...with...
let i = 0;
const doStuff = () => {
// do stuff
if (++i < data.length) {
setTimeout(doStuff, 40);
}
};
setTimeout(doStuff, 40);
Related
I'm trying to show the process of how the backtracking algorithm solves a sudoku board, but I'm not sure how I can make sure that it only gets called every 500ms.
function solveBoard(board) {
// I tried doing setTimeout from here to the bottom, but it breaks the solver and just puts 9s everywhere.
let empty = findEmpty(board);
if (!empty) return true;
let row = empty[0];
let col = empty[1];
for (let i = 1; i < 10; i++) {
board[row][col] = i;
console.log(board[row][col]);
document.getElementById(`${row}-${col}`).value = i;
if (checkValid(board, row, col)) {
if (solveBoard(board)) {
return true;
}
}
board[row][col] = 0;
}
return false;
}
The first time I call solve board is just an event listener.
solveBtn.addEventListener("click", () => {
solveBoard(boardArray);
});
Call sleep in solveBoard
async function solveBoard(board) {
await sleep()
// …
}
function sleep(ms = 500) {
return new Promise(resolve => setTimeout(resolve, ms))
}
// Demo
(async () => {
console.log('a')
await sleep()
console.log('b')
await sleep()
console.log('c')
})();
I am making a screensaver that displays the elements inside an array with a delay between each iteration, to display the elements slowly one by one. My "onmousemove" event successfully removes the screensaver from the page, but the for loop in my startScreensaver() function keeps running when it should in fact break. What am I doing wrong?
see JSfiddle https://jsfiddle.net/m60k75jf/
const idletime = 2;
const screenSaver = document.getElementById("screensaver");
const stars = Array.from(document.querySelectorAll(".star"));
let mousetimeout;
let screensaverActive = false;
window.addEventListener("mousemove", (e) => {
clearTimeout(mousetimeout);
if (screensaverActive) {
stopScreensaver();
} else {
mousetimeout = setTimeout(function () {
startScreensaver();
}, 1000 * idletime);
}
});
function stopScreensaver() {
screensaverActive = false;
stars.forEach((star) => {
star.classList.remove("is--visible");
});
screenSaver.classList.remove("is--active");
}
function startScreensaver() {
screensaverActive = true;
screenSaver.classList.add("is--active");
for (let index = 0; index < stars.length; index++) {
if (screensaverActive) {
setTimeout(function () {
stars[index].classList.add("is--visible");
}, 2000 * index);
} else {
break;
}
}
}
You can't break the loop like that. Your loop creates all these setTimeout immediately. Your condition will just never trigger. What you'll need to do is clear out all those setTimeout. You can push them into an array.
let animationTimeouts;
function startScreensaver() {
animationTimeouts = [];
screensaverActive = true;
screenSaver.classList.add("is--active");
for (let index = 0; index < stars.length; index++) {
if (screensaverActive) {
animationTimeouts.push(setTimeout(function () {
stars[index].classList.add("is--visible");
}, 2000 * index));
} else {
break;
}
}
}
Then clear them out on stopScreensaver
function stopScreensaver() {
screensaverActive = false;
if(animationTimeouts) animationTimeouts.forEach(clearTimeout);
stars.forEach((star) => {
star.classList.remove("is--visible");
});
screenSaver.classList.remove("is--active");
}
You might also want to reconsider moving your CSS transition to .star.is--visible
I need a loop to play 10 sounds in sequence. My first attempt had the sounds overlapping, so I was told that I need to use Promise/await. The code below plays sound 0 then never continues the loop.
(The library I'm using (jscw) is for morse code. You pass it a string, it plays the morse equivalent. Its "onFinished" calls a user-defined function.)
async function playAll() {
for (let i = 0; i < 10; i++) {
playMorse(words[i]);
await playstate();
}
}
function playstate() {
playdone = true;
//console.log(playdone);
return new Promise((resolve) => {
window.addEventListener('playdone', resolve)
})
}
function playMorse(z) {
var m = new jscw();
playdone = false;
m.onFinished = function() {
playstate();
}
m.play(z);
}
It seems nothing is supposed to fire the playdone event you are listening for.
So a simple solution is to fire it in the onFinished callback.
const words = ["hello", "world"];
async function playAll() {
for (let i = 0; i < 2; i++) {
console.log("###READING", words[i]);
playMorse(words[i]);
await playstate();
}
}
function playstate() {
playdone = true;
//console.log(playdone);
return new Promise((resolve) => {
window.addEventListener('playdone', resolve, { once: true })
})
}
function playMorse(z) {
var m = new jscw();
playdone = false;
m.onFinished = function() {
dispatchEvent( new Event("playdone") );
}
m.play(z);
}
btn.onclick = playAll;
<script src="https://fkurz.net/ham/jscwlib/src/jscwlib.js"></script>
<button id="btn">play all</button>
But you don't need an event here, simply make playMorse return a Promise that will resolve in the onFinished callback:
const words = ["hello", "world"];
async function playAll() {
for (let i = 0; i < 2; i++) {
console.log("###READING", words[i]);
await playMorse(words[i]);
}
}
function playMorse(z) {
return new Promise( (resolve) => {
const m = new jscw();
m.onFinished = resolve;
m.play(z);
});
}
btn.onclick = playAll;
<script src="https://fkurz.net/ham/jscwlib/src/jscwlib.js"></script>
<button id="btn">play all</button>
I am trying to implement two classes that can deal with asynchronous tasks in JavaScript:
Class Task: mimics the execution of a task with setTimeout. Once the timer expires, the task is considered completed.
Class TaskManager: has a capacity parameter to limit the numbers of tasks that can be executing in parallel.
I thought if I could just call the loop function recursively, just to keep checking if one job is done, I could proceed to the next job. But this leads immediately to a "Maximum call stack size exceeded" error.
Can someone explain how I can fix this?
class Task {
constructor(time) {
this.time = time;
this.running = 0;
}
run(limit, jobs, index) {
setTimeout(() => {
console.log('hello', index);
this.done(limit, jobs, index);
}, this.time);
}
done(limit, jobs, index) {
jobs.splice(index, 1);
console.log(jobs);
}
}
class TaskManager {
constructor(capacity) {
this.capacity = capacity;
this.jobs = [];
this.index = 0;
this.running = 0;
this.pending = [];
}
push(tk) {
this.jobs.push(tk);
this.index += 1;
const loop = () => {
if (this.jobs.length === 0) {
return;
}
if (this.jobs.length <= this.capacity) {
this.running += 1;
tk.run(this.capacity, this.jobs, this.index-1);
return;
}
loop();
}
loop();
}
}
const task = new Task(100);
const task1 = new Task(200);
const task2 = new Task(400);
const task3 = new Task(5000);
const task4 = new Task(6000);
const manager = new TaskManager(3);
manager.push(task);
manager.push(task1);
manager.push(task2);
manager.push(task3);
manager.push(task4);
You should not implement the busy loop, as that will block the event loop and so no user UI events or setTimeout events will be processed.
Instead respond to asynchronous events.
If you let the setTimeout callback resolve a Promise, it is not so hard to do.
I modified your script quite drastically. Here is the result:
class Task {
constructor(id, time) {
this.id = id;
this.time = time;
}
run() {
console.log(this + ' launched.');
return new Promise(resolve => {
setTimeout(() => {
console.log(this + ' completed.');
resolve();
}, this.time);
});
}
toString() {
return `Task ${this.id}[${this.time}ms]`;
}
}
class TaskManager {
constructor(capacity) {
this.capacity = capacity;
this.waiting = [];
this.running = [];
}
push(tk) {
this.waiting.push(tk);
if (this.running.length < this.capacity) {
this.next();
} else {
console.log(tk + ' put on hold.');
}
}
next() {
const task = this.waiting.shift();
if (!task) {
if (!this.running.length) {
console.log("All done.");
}
return; // No new tasks
}
this.running.push(task);
const runningTask = task.run();
console.log("Currently running: " + this.running);
runningTask.then(() => {
this.running = this.running.filter(t => t !== task);
console.log("Currently running: " + this.running);
this.next();
});
}
}
const a = new Task('A', 100);
const b = new Task('B', 200);
const c = new Task('C', 400);
const d = new Task('D', 5000);
const e = new Task('E', 6000);
const manager = new TaskManager(3);
manager.push(a);
manager.push(b);
manager.push(c);
manager.push(d);
manager.push(e);
I'm trying to understand how Promises are working and I can't get my piece of code to work.
class Lights {
constructor(delay) {
this.blue = 0;
this.green = 0;
this.red = 0;
this.delay = delay;
}
fadeIn(color, i) {
var self = this;
return new Promise(function(resolve, reject) {
setTimeout(function () {
self[color] = i;
console.log(self[color]);
i+=5;
if (i <= 255) {
self.fadeIn(color, i);
}
resolve(self);
}, self.delay);
});
}
fadeOut(color, i) {
var self = this;
return new Promise(function(resolve, reject) {
setTimeout(function () {
self[color] = i;
console.log(self[color]);
i-=5;
if (i >= 0) {
self.fadeIn(color, i);
}
resolve(self);
}, self.delay);
});
}
}
var lights = new Lights(50);
lights.fadeIn("blue", 0).then(
lights.fadeOut("blue", 255)
);
Here is a jsFiddle of the code.
The idea behind the code is to set the blue color from 0 to 255 and Then from 255 to 0. How can I do this ?
You are making recursive calls so on the last call what you resolve is not the resolve in your first promise that you call then on so you could store that first resolve in one property in your class and then call it.
class Lights {
constructor(delay) {
this.blue = 0;
this.green = 0;
this.red = 0;
this.delay = delay;
this.fadeInResolve = null;
this.fadeOutResolve = null;
}
fadeIn(color, i) {
return new Promise((resolve, reject) => {
if (!this.fadeInResolve) {
this.fadeInResolve = resolve
}
setTimeout(() => {
this[color] = i;
console.log(this[color]);
i += 5;
if (i <= 255) this.fadeIn(color, i);
else this.fadeInResolve(this)
}, this.delay);
});
}
fadeOut(color, i) {
return new Promise((resolve, reject) => {
if (!this.fadeOutResolve) {
this.fadeOutResolve = resolve
}
setTimeout(() => {
this[color] = i;
console.log(this[color]);
i -= 5;
if (i >= 0) this.fadeOut(color, i);
else this.fadeOutResolve(this)
}, this.delay);
});
}
}
var lights = new Lights(50);
lights.fadeIn("blue", 0).then(() => {
console.log('Fade in done')
lights.fadeOut("blue", 255).then(() => {
console.log('Fade out done')
})
});
Promise.prototype.then() should take a callback function and the recursion is not waiting. Consider this code which can be used to do the same thing:
//promisify :)
function timer(delay) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve();
}, delay);
});
}
class _Modulator {
constructor(_resolution = 255, _delay = 5) {
/* assert resolution and delay > 0; */
this._resolution = _resolution;
this._delay = _delay;
this._counter = 0;
this._running = false;
}
start() {
console.log("timer start");
this._running = true;
this._start();
}
_start() {
return timer(this._delay).then(() => {
if (this._running === true) {
this._counter += 1;
console.log("tick");
this._onTick();
/* care should be taken to ensure this will always catch, e.g.,
* correcting init
*/
if (this._counter === this._resolution) {
this._counter = 0;
this._onCycle();
}
this._start();
}
});
}
stop() {
this._running = false;
console.log("timer stopped");
}
_onTick() {
console.log("tick handle: %s", this._counter);
}
_onCycle() {
console.log("new cycle");
}
}
class UpDownModulator extends _Modulator {
constructor(_resolution = 255, _delay = 5) {
super(_resolution, _delay);
this._dir = 1;
}
_onTick() {
console.log("tick handle: %s", this.getCounter());
}
_onCycle() {
this._toggleDirection();
console.log("new cycle: going %s", this.getDirection());
}
_toggleDirection() {
this._dir ^= 1;
}
getCounter() {
return this._dir
? this._counter
: this._resolution - this._counter;
}
getDirection() {
return this._dir ? "up" : "down";
}
}
let c = new UpDownModulator();
c.start();
You can create a ColorFader class that depends on a Modulator and observe it. This creates clean abstractions that adhere to SRP.
I hope this helps!