Home > Net >  stop current run of useEffect and start the next one
stop current run of useEffect and start the next one

Time:11-21

I was wondering if there is any way to break the current process of a UseEffect and have it start on the next render, like this

...
  useEffect(() => {
   SlowFunction(update);
  }, [update]);

setUpdate(1)
// a bit of time passes but not long enough for the SlowFunction(1) to be done
setUpdate(2)
//when this is called and the useEffect runs, stop the SlowFunction(1) and run SlowFunction(2)

my updated personal function is called in the use effect like so,

const [update, setUpdate] = useState(0);
  const [thisConst, setThisConst] = useState(0);

  async function SlowFunction(firstParam, paramEtc, { signal } = {}) {
    while (true) {
      //wait two seconds between each
      await new Promise((r) => setTimeout(r, 2000));

      // Before starting every individual "task" in the function,
      // first throw if the signal has been aborted. This will stop the function
      // if cancellation occurs:
      signal?.throwIfAborted();

      // else continue working...
      console.log('working on another iteration');
    }
    console.log('Completed!');
    return 'some value';
  }

  useEffect(() => {
    const controller = new AbortController();
    const { signal } = controller;

    (async () => {
      try {
        const result = await SlowFunction(update, 'some other value', {
          signal,
        });
        setConst(result);
      } catch (ex) {
        console.log('EXCEPTION THROWN: ', ex);
      }
    })();

    return () => controller.abort(new Error('Starting next render'));
  }, [update]);

CodePudding user response:

The AbortSignal API is the standard method for handling cancellation.

I'll provide an example of how to use it with a function like your SlowFunction. You'll need to accept an abort signal as an optional parameter so that when the next render occurs, the function can be cancelled.

Here's an example cancellable function:

async function SlowFunction (firstParam, paramEtc, {signal} = {}) {
  for (let i = 0; i < 1_000_000; i  = 1) {
    // Before starting every individual "task" in the function,
    // first throw if the signal has been aborted. This will stop the function
    // if cancellation occurs:
    signal?.throwIfAborted();

    // else continue working...
    console.log('working on another iteration');
  }

  return 'some value';
}

You can use it in an effect hook like this: returning a cleanup function which invokes the abort method on the controller:

useEffect(() => {
  const controller = new AbortController();
  const {signal} = controller;

  (async () => {
    try {
      const result = await SlowFunction(update, 'some other value', {signal});
      setConst(result);
    }
    catch (ex) {
      // Catch the exception thrown when the next render starts
      // and the function hasn't completed yet.

      // Handle the exception if you need to,
      // or do nothing in this block if you don't.
    }
  })();

  return () => controller.abort(new Error('Starting next render'));
}, [update]);

If the function completes before the next render occurs, then the abort operation will have no effect, but if it hasn't yet, then the next time that the statement signal?.throwIfAborted(); is reached, the function will throw an exception and terminate.

  • Related