Home > Mobile >  JavaScript: Interrupt a setTimeout() inside a promise, upon a click event
JavaScript: Interrupt a setTimeout() inside a promise, upon a click event

Time:01-09

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.

CodePudding user response:

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>

CodePudding user response:

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