最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

How to pause and resume function execution in javascript - Stack Overflow

programmeradmin4浏览0评论

I have this function:

const BFS = (graph, start) => {
  let queue = []
  queue.push(start)

  let visited = []
  visited[start] = true

  while (queue.lenght > 0) {
    let node = queue.shift()
    for (var i=1; i<graph[node].length; i++) {
      if (graph[node][i] && !visited[i]) {
        visited[i] = true
        queue.push(i)
      }
    }
  }
}

What I want is to have a button that when I press it, It stops the execution of the function, and if I press it again, it resumes the execution from the same place it stopped on.

Is that possible ? and if so, how to do it ?

I have this function:

const BFS = (graph, start) => {
  let queue = []
  queue.push(start)

  let visited = []
  visited[start] = true

  while (queue.lenght > 0) {
    let node = queue.shift()
    for (var i=1; i<graph[node].length; i++) {
      if (graph[node][i] && !visited[i]) {
        visited[i] = true
        queue.push(i)
      }
    }
  }
}

What I want is to have a button that when I press it, It stops the execution of the function, and if I press it again, it resumes the execution from the same place it stopped on.

Is that possible ? and if so, how to do it ?

Share Improve this question asked May 30, 2021 at 14:56 Ayman TarigAyman Tarig 751 silver badge7 bronze badges 8
  • I don't think there is such a way to do so, other than using breakpoints. I am guessing you are trying to debug the code? If this is the case, make sure that the word length is correctly spelled in the line while (queue.lenght > 0) {. – Adnan Commented May 30, 2021 at 15:09
  • 1 What you want is not possible. The closest is to attach a click event to the button to run some JS. When you're runnign a JS code, everything else in a browser is blocked, until the JS has been executed, there's no way to detect a button click during the JS execution. – Teemu Commented May 30, 2021 at 15:16
  • @Adnan I'm not trying do debug the code .. the thing is this function is a part of visualization process, and I want the user to be able to stop the visualization whenever he wants then resume it again. – Ayman Tarig Commented May 30, 2021 at 15:16
  • @MikeM It is OK to make changes to the BFS function, if that change solves the issue – Ayman Tarig Commented May 30, 2021 at 15:20
  • @Teemu, it is possible - with generator functions, checkout my solution. – Monsieur Merso Commented May 30, 2021 at 18:58
 |  Show 3 more ments

4 Answers 4

Reset to default 7

Other solution with generator functions, read about this feature on MDN

Note, that here we can proceed further one step at a time!

General idea:

  1. Put yield statements in your method, in places where you want to pause it.
  2. Create instance of your generator and write code that will call its .next() method and will handle repeated calls.
  3. Note, that you can get values from generator and also pass in with .next() method.

// generator function that we want to stop/continue in the middle
function* stoppableMethod() {
  // here is the implementation of the algorithm
  // that we want to control
  let i = 0;
  while (true) {
    // note that yield is inside infinite loop!
    yield i;
    i += 1;
  }
}

const generatorInstance = stoppableMethod();

// tick generator and perform update of the indicator
const nextStep = () => {
  const { value } = generatorInstance.next();
  document.getElementById("indicator").innerHTML = value;
}

// state to keep track of the setInterval id
const state = {
  timeoutId: 0,
}

// start method progression
const start = () => {
  // do not start interval if there is already an interval
  // running
  if (state.timeoutId === 0) {
    state.timeoutId = setInterval(() => nextStep(), 1000)
  }
}

// clear timeout to stop auto porgress
const stop = () => { 
  clearTimeout(state.timeoutId);
  state.timeoutId = 0;
}

// tick further one step
const stepForward = () => nextStep()
<button onclick="start()">start</button>
<button onclick="stop()">pause</button>
<button onclick="nextStep()">one step forward</button>
<div id="indicator">0</div>

To pause and resume the execution of a function at a particular place at the click of a button, we can use either yield within a generator function or await a Promise within an async function.

Either way, we need to yield to or await an asynchronous function like setTimeout, setInterval or requestIdleCallback to give the single thread of JavaScript execution the opportunity to execute any event-handler callbacks that can then control when the paused function is resumed.

See the JavaScript event loop to understand this further.

Suppose we have a button and a function f that we want to be able to pause at the line shown.

function f() {
  let i = 0;
  while (true) {
    // pause here when button clicked
    button.textContent = ++i;
  }
}

If using yield then f could be amended to

function* f() {
  let i = 0;
  while (true) {
    yield;
    button.textContent = ++i;
  }
}

We would then create an iterator from this generator function and use iterator.next() in an asynchronously executed callblack to resume execution.

If using await then f could instead be amended to

async function f() {
  let i = 0;
  while (true) {
    await new Promise(executor);
    button.textContent = ++i;
  }
}

where executor is a function which calls an asynchronous function that resolves the executor to resume execution from the await.


Some examples:

Using a generator function and setTimeout.

const button = document.querySelector('button');

let started = false;
let iterator = f();

const nextTick = () => {
  if (started) {
    iterator.next();
    setTimeout(nextTick);
  }
};

button.addEventListener('click', () => {
  started = !started;
  nextTick();
});

function* f() {
  let i = 0;
  while (true) {
    yield;
    button.textContent = ++i;
  }
}
button { 
  text-align: center; padding: .5rem; width: 16rem;
  font-size: 2rem; border-radius: .5rem; margin-left: .25rem;
}
<button>Click me</button>

Using async and await:

const button = document.querySelector('button');

let started = false;
let resolve;

const nextTick = () => new Promise(res => {
  resolve = res;
  setTimeout(() => {
    if (started) resolve();
  });
});

button.addEventListener('click', () => {
  started = !started;
  if (started) resolve();
});

async function f() {
  let i = 0;
  while (true) {
    await nextTick();
    button.textContent = ++i;
  }
}

f();
button { 
  text-align: center; padding: .5rem; width: 16rem;
  font-size: 2rem; border-radius: .5rem; margin-left: .25rem;
}
<button>Click me</button>

Using setInterval, which is basically the same as using setTimeout but less flexible:

const button = document.querySelector('button');

let id = 0;
let started = false;
const iterator = f();
const next = iterator.next.bind(iterator);

button.addEventListener('click', () => {
  started = !started;
  if (started) {
    if (id === 0) id = setInterval(next, 0);
  } else if (id !== 0) {
    clearInterval(id);
    id = 0;
  }
});

function* f() {
  let i = 0;
  while (true) {
    yield;
    button.textContent = ++i;
  }
}
button { 
  text-align: center; padding: .5rem; width: 16rem;
  font-size: 2rem; border-radius: .5rem; margin-left: .25rem;
}
<button>Click me</button>


The problem with using asynchronous functions like setTimeout or setInterval here is that they are subject to a minimum delay of several milliseconds, which can be seen in how slow the number increments in the examples above.

Notice how much faster the number increments using a MessageChannel to fire the button-enabling asynchronous callback.

const button = document.querySelector('button');

let nextTick;

button.addEventListener('click', (() => {
  let started = false;
  let resolve;
  const { port1, port2 } = new MessageChannel();
  port2.onmessage = () => {
    if (started) resolve();
  };
  nextTick = () => new Promise(res => {
    resolve = res;
    port1.postMessage(null);
  });
  return () => {
    started = !started;
    if (started) resolve();
  };
})());

async function f() {
  let i = 0;
  while (true) {
    await nextTick();
    button.textContent = ++i;
  }
}

f();
button { 
  text-align: center; padding: .5rem; width: 16rem;
  font-size: 2rem; border-radius: .5rem; margin-left: .25rem;
}
<button>Click me</button>

Note that in these examples nextTick could have been named anything, it is not the same as the process.nextTick of Node.js. See setImmediate if using Node.js.

If you are animating you may be using requestAnimationFrame and that also executes its callback asynchronously, so it can be used here.

The following shows the relative speed of a variety of asynchronous functions:

const button = document.querySelector('button');

button.addEventListener('click', (() => {
  const setImmediateAnalogues = [
    setTimeout,
    requestAnimationFrame,
    cb => requestIdleCallback(cb, { timeout: 0 }),
    cb => {
      window.onmessage = event => {
        // event.origin should be validated here
        if (event.source === window) cb();  
      };
      window.postMessage('', window.location);
    },
    (() => {
      const { port1, port2 } = new MessageChannel();
      return cb => {
        port2.onmessage = cb;
        port1.postMessage('');
      };
    })(),
  ];
  
  let setImmediate = setTimeout;
  for (const rb of document.querySelectorAll('input[name="rb"]')) {
    const analog = setImmediateAnalogues.shift();
    rb.addEventListener('click', () => setImmediate = analog);
  }
  
  const iterator = f();
  let started = false;
  const nextTick = () => {
    if (started) {
      iterator.next();
      setImmediate(nextTick);
    }
  };
  
  return () => {
    started = !started;
    nextTick();
  };
})());


function* f() {
  let i = 0;
  while (true) {
    yield;
    button.textContent = ++i;
  }
}
button { text-align: center; padding: .5rem; width: 16rem;
font-size: 2rem; border-radius: .5rem; margin-left: .25rem; }
label { padding: .1rem; display: block; font-family: monospace; }
<label><input type='radio' name='rb' value='' checked> setTimeout</label>
<label><input type='radio' name='rb' value=''> requestAnimationFrame</label>
<label><input type='radio' name='rb' value=''> requestIdleCallback</label>
<label><input type='radio' name='rb' value=''> window.postMessage</label>
<label><input type='radio' name='rb' value=''> MessageChannel</label>
<br>
<button>Click me</button>

You can use this but i am not sure if this helps you
as far as I know other than this you cant stop running function from outside

document.getElementById("start").addEventListener("click", startInterval);
document.getElementById("stop").addEventListener("click", stopInterval);

// You'll need a variable to store a reference to the timer
var timer = null;

function startInterval() {
  // Then you initilize the variable
  timer = setInterval(function() {
    console.log("Foo Executed!");
  }, 800);
}

function stopInterval() {
  // To cancel an interval, pass the timer to clearInterval()
  clearInterval(timer);
}
<button type="button" id="start">Start</button>
<button type="button" id="stop">Stop</button>

Everything is possible. However you have to keep a state. I mean you give a state to your function to start with and it does it things up until you click a button and then it saves the state and stops. Once you click the button and change the state to active again it continues from where it left.

In this particular case your state could be;

var state = { active: false
            , graph : myGraph
            , node  : currentNode
            , index : i
            }

Since you are basically doing the thing in the while loop i would add a statement like;

while (queue.lenght > 0 && state.active)

and once somebody clicks the button and toggles state.active to false the while loop, terminates and at that point, i mean right after the while loop, you save your new state. So obviously the next time you active this function your code should be able to start from wherever it was left.

Here is a simple mock up to show how it might be implemented;

var state = { active: false
            , count : "0"
            };

document.querySelector("#b")
        .onclick = function(){
                     let run = n => setTimeout( n => ( this.textContent = n.toString()
                                                                           .padStart(6,"0")
                                                     , state.active && run(++n)
                                                     )
                                              , 100
                                              , n
                                              );
                     state.active ? ( state.active = false
                                    , state.count  = this.textContent
                                    )
                                  : ( state.active = true
                                    , run(state.count)
                                    );
                  };
<button id="b">000000</button>

发布评论

评论列表(0)

  1. 暂无评论