Reset a setTimeout() inside a promise, upon a click event - javascript

Here is a very simplified reproduction of the issue I am facing:
window.onload=function(){
touchme = document.getElementById('click');
function mains(){
touchme.innerHTML = "clicked " + Math.random().toString();
}
function process() {
touchme.dataset.clicked = "false";
mains();
touchme.addEventListener("click", () => {
touchme.dataset.clicked = "true";
}, {once : true});
let clickPromise = new Promise((resolve, reject) => {
var timer = setTimeout(() => {
if(touchme.dataset.clicked == "true"){
resolve();
}
else{
reject();
}
}, 1500);
});
clickPromise.then(() => {
process();
});
clickPromise.catch(() => {
alert("game over");
});
}
process();
}
This bit of HTML has been used <body><button id="click">Click</button></body>
What I basically want is:
if I click the button, mains() will run immediately
if I don't, setTimeout() will wait 1.5secs for me. Within this time if I click, setTimeout() resets and mains() gets executed
if I still don't click, alert("game over") is executed. The "self callback loop" breaks
However, the mains() function isn't being run immediately whenever I am clicking, instead it is waiting for the timeout, causing a delay of 1.5s after every click.
Naturally, the solution is to use clearTimeout(), however, I can't seem to wrap my head around where to use it. Adding it inside the function argument of event listener causes the timeout to run independently of the click event and just makes it reject the promise 1.5s later, notwithstanding my button clicks. I also tried calling the function itself inside the event listener function, which doesn't work. Adding it inside an if(touchme.dataset.clicked == "true") outside the setTimeout() and inside the promise wouldn't work, as my initial value is false, so it just checks the initial state.

You really don't need to use promises for this, just a simple handler function will make it a lot easier to clear and reset the timeout:
let lost = false;
function timeoutHandler() {
alert("game over");
lost = true;
}
let timeout = setTimeout(timeoutHandler, 1500);
document.getElementById('click').addEventListener('click', () => {
if (lost) return;
clearTimeout(timeout);
timeout = setTimeout(timeoutHandler, 1500);
});
<button id="click">Click</button>

This makes a good use case for Promise.race:
async function main() {
while (await game()) {}
}
async function game() {
let clickPromise = new Promise(res =>
document.querySelector('button').onclick = () => res(true))
let timerPromise = new Promise(res =>
setTimeout(() => res(false), 1500))
let ok = await Promise.race([clickPromise, timerPromise])
document.querySelector('button').textContent = ok
? 'keep clicking ' + Math.random()
: 'game over'
return ok
}
window.onload = main
<button>click</button>

Related

Using JS Promise as an interrupt

I wanted to directly call a function (like interrupt handler) when a certain condition is met. I didn't want to using "polling" for that as it increases time complexity.
count = 1
p = new Promise((resolve, reject)=>{
if(count == 2){
resolve("hello")
}
});
p.then((msg)=>{
console.log(msg)
})
console.log("1 now");
count = 2;
I expected console.log(msg) to run when count=2 but this is not the case. It turned out that the promise is still "pending". What is the reason this happens? And how do I implement my question.
You can use a Proxy to listen variable changes.
const count = new Proxy({
value: 0
}, {
set(target, prop, val) {
// set value
target[prop] = val;
console.log(`count is ${val}`);
// stop condition
if (val == 2) {
console.log(`count is 2(trigger stop condition)`);
}
}
});
// wait 2 seconds and change count.value to 1
setTimeout(() => count.value = 1, 2000);
// wait 2 seconds and change count.value to 2
// it triggers the stop condition
setTimeout(() => count.value = 2, 2000);
console.log("Waiting for changes ...");
reference: Listen to js variable change
Proxy is one of the solutions for this. But I post another approach for your case.
You can define a custom class or object, and work with that class. Also you register your custom listener for it, and do whatever.
This is a sample of my code. Maybe it will give you some ideas for your solution.
class MyObject {
constructor(value, func) {
this._value = value;
this._listener = func;
}
get value() {
return this._value;
}
set value(newValue) {
this._listener(newValue);
this._value = newValue;
}
}
function customListener(changedValue) {
console.log(`New Value Detected: ${changedValue}`);
}
const count = new MyObject(1, customListener);
count.value = 2;
The issue you're having is that the code inside the Promise resolves synchronously. It seems like you are assuming Promises are by default async, but that is a common async misconception. So, the code
if(count == 2){
resolve("hello")
}
resolves synchronously (that is, right after you declare count to be 1) so the Promise will never be resolved. If you want to asynchronously check for a condition without using libraries, you can use setInterval:
function checkForCondition(count, time){
return new Promise(resolve => {
const interval = setInterval(() => {
if (count == 2){
resolve("The count is 2!");
}
} , time);
});
}
If you call this function, the callback inside setInterval will be placed on the event loop every x ms, where x is equal to the time parameter.

SetTimeout not working inside async eventListener

In the following function I am adding an eventlistener to every li, up until the setTimeout everything work as expected. Neither the console.log nor the populateForm() execute. I have tried a bunch of different methods but nothing seams to work.
async function addClickListener() {
const items = document.querySelectorAll(".li");
items.forEach((item) => {
item.addEventListener("click", async (evt) => {
evt.preventDefault();
sessionStorage.clear();
const movieData = await createMovieObject(evt.currentTarget);
sessionStorage.setItem("movieData", JSON.stringify(movieData));
clearMovieList();
removeHidden();
switchToSearch();
setTimeout(function () {
console.log("hello");
populateForm();
}, 5000);
});
});
}
I tried calling await delay(5000); instead of the setTimeout like above but this doesn't work either
const delay = (ms) =>
new Promise(() => {
setTimeout(() => {
console.log("hello");
populateForm();
}, ms);
});
Here is the populateForm() function
function populateForm() {
const check = document.querySelector("#added_movies");
console.log(sessionStorage);
console.log(check);
}
Here is the switchToSearch() function, it seams that this is causing the issue as I am changing links and the console is maybe reset (just an assumption)?!
function switchToSearch() {
window.location.href = "./movies";
}
With window.location.href = "./movies"; a new document is loaded and all Timeouts, EventListeners, … are released. So your registered timeout won’t be executed.
I have marked #t.niese ansewer as accepted since this is the underlying problem I am facing. One thing that I would like to note though is that although code in the setTimeout() did not work, code that followed, for example an console.log('I am on the new page') would work right after the switchToSearch();.
Anyway, I have continued now and made it work via an onload:
if (window.location.pathname == "/movies") {
window.onload = populateForm;
console.log(sessionStorage);
}

How can I await an event listener inside a function?

I'm trying to wait for a button to be clicked before proceeding inside another function.
async function foo() {
// code before
await ??? // wait for start button to be clicked before proceeding
// code after
}
startButton.addEventListener("click", function() {
return new Promise(); // ???
}, false);
How can I use an event listener inside another function?
You're on the right track with returning a new Promise(). You can do something along these lines:
async function btnClick(btn) {
return new Promise(resolve => btn.onclick = () => resolve());
}
then in your calling code:
await btnClick(startButton);
You can proceed like this:
// click the first button to begin the process
document.getElementById('beginbtn').addEventListener('click', function () {
foo();
});
// foo
async function foo() {
// hide the begin button
document.getElementById('beginbtn').style.display = 'none';
// show the pass button
document.getElementById('passbtn').style.display = 'block';
/******* FOCUS HERE ******/
console.log('-- begin ... click "PASS" to continue -- ');
await bar();
console.log('-- end -- ');
/******* FOCUS HERE ******/
// show the begin button
document.getElementById('beginbtn').style.display = 'block';
// hide the pass button
document.getElementById('passbtn').style.display = 'none';
}
// bar
function bar() {
return new Promise(function (resolve, reject) {
document.getElementById('passbtn').addEventListener('click', function () {
console.log('clicked !!!');
resolve();
});
});
}
<button id="beginbtn">CLICK TO BEGIN</button>
<button id="passbtn" style="display:none;">PASS</button>
Two buttons: begin and pass. First the begin button is shown and pass button hidden. Once clicked, the begin button starts the foo(). The process then starts and wait for bar to be resolved by clicking on pass button(which is now shown while begin button is hidden) to continue until end.
Hope it will help !
you can use an anonymous function and call it itself.
await (async () => {
return new Promise((res) => {
btn.onclick= () => res(true);
});
})();
use like this
async function foo() {
await (async () => {
return new Promise((res) => {
btn.onclick= () => res(true);
});
})();
//do whatever
}
You may be able to get this to work, but probably shouldn't.
Having one large function that does stuff, waits for input, and then does more stuff is cumbersome and hard to reason about.
Event listeners are there to help you with the "waiting for user input" problem without having to wrangle your code around it. By having a function start and then wait for an event listener to fire, you are kind of undoing that pattern.
In fact, many programmers consider having to create the first Promise in a chain to be an "anti-pattern", which means it goes against the grain of good practices. But the bigger problem here is how the bulk of this one big function will weigh you down in the future.
Instead, try splitting up your function. For instance:
function preInput(){
//some setup tasks here
}
function postInput(){
//some post-input tasks here
}
preInput(); // or elsewhere, depending on needs
startButton.addEventListener("click", postInput);
That's a lot easier to follow, and add to, in the future.

Calling a function as many times as possible in a given time interval

I am trying to call the function test() as many times as possible in a given time interval.
Here the function should be running for 15 seconds.
function test(): void; // Only type def
function run() {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, 15000); // 15 seconds
while (true) {
test();
}
});
}
run()
.then(() => {
console.log('Ended');
});
However, the function doesn't stop running, and the Ended console.log does not appear. (Promise not resolved obviously). Is there a way to achieve this in Javascript ?
I was wondering, I could probably use console timers and put the condition in the while statement ? (But is that the best way ?)
The reason why your function does not stop executing is because resolving a promise does not stop script executing. What you want is to store a flag somewhere in your run() method, so that you can flip the flag once the promise is intended to be resolved.
See proof-of-concept below: I've shortened the period to 1.5s and added a dummy test() method just for illustration purpose:
let i = 0;
function test() {
console.log(`test: ${i++}`);
}
function run() {
return new Promise(resolve => {
let shouldInvoke = true;
setTimeout(() => {
shouldInvoke = false;
resolve();
}, 1500); // 15 seconds
const timer = setInterval(() => {
if (shouldInvoke)
test();
else
window.clearInterval(timer);
}, 0);
});
}
run()
.then(() => {
console.log('Ended');
});

Loop on a promise indefinitely until rejection

I have a function that does some async work and returns a Promise, and I want to execute this function indefinitely until the promise is rejected.
Something like the following :
doSomethingAsync().
.then(doSomethingAsync)
.then(doSomethingAsync)
.then(doSomethingAsync)
// ... until rejection
I made a little CodePen so I can test potential solutions : http://codepen.io/JesmoDrazik/pen/pbAovZ?editors=0011
I found several potential answers but nothing seems to work for my case.
If anyone has a solution, I'd be glad, because I just can't find anything !
Thanks.
You can do
(function loop(){
doSomethingAsync().then(loop);
})();
But looking at your pen it's not clear where the rejection should come from. If you want to stop repeating an operation when the user clicks a button, you can change a state in the button handling and then do
(function loop(){
doSomethingAsync().then(function(){
if (!stopped) loop();
});
})();
Made a modification to your codepen
var FAKE_COUNT = 0;
function doSomething() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('doing something async: ' + FAKE_COUNT)
if (FAKE_COUNT < 10) {
resolve();
} else {
reject();
}
FAKE_COUNT ++;
}, 1000);
});
}
const button = document.querySelector('.js-button');
button.addEventListener('click', () => {
// maybe we can do something here ?
})
function test() {
doSomething().then(test).catch(() => {
console.log("Rejected")
});
}
test();
FAKE_COUNT just becomes the flag, so if you want to stop the promise with a click instead of a count you could just make it a bool and check that when executing the async task
Depending on where you want the logic and rejection to take place, but assuming you want the promise itself to be self executing (otherwise the asynchronous execution itself could loop) it could return itself as a new promise:
function doSomething() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('doing something async');
resolve();
}, 1000);
}).then(()=>doSomething());
}
For the rejection part, the easiest way would be to introduce a flag, (although that would make the promise a little less self containing):
var stop = false;
function doSomething() {
return new Promise((resolve, reject) => {
if(stop)
reject();
else{
setTimeout(() => {
console.log('doing something async');
resolve(); //(check for stop could be done here as well)
}, 1000);
}
}).then(()=>doSomething());
}
const button = document.querySelector('.js-button');
button.addEventListener('click', () => {
stop = true;
});
doSomething();

Categories

Resources