Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

setTimeout runs its callback codes much slower than without it

We are struggling with a strange issue in using setTimeout function in Javascript at the React.
As using codes inside the setTimeout, it runs much slower than running the codes without the setTimeout!

As a comparison, the performance is resulted in:

Using setTimeout: 1391 ms
without using setTimeout: 15 ms

In API callback (eg. axios), the setTimeout version happens as well!


A simple sample code is shown in the following:
codesandbox

Can anyone explain what is happening?

import React, { useState } from "react";
import ReactDOM from "react-dom";

function App() {
  const [value, setValue] = useState("");
  const [time, setTime] = useState("");

  const startSample = () => {
    const startTimeStamp = new Date().valueOf();
    for (let i = 0; i < 5000; i++) {
      setValue(`test-${i}`);
      // console.log(`test-${i}`);
    }
    const endTimeStamp = new Date().valueOf();
    setTime(`${endTimeStamp - startTimeStamp}ms`);
  };

  const handleClick1 = () => {
    startSample();
  };

  const handleClick2 = () => {
    setTimeout(() => {
      startSample();
    });
  };

  return (
    <div style={{ textAlign: "left" }}>
      <p>{value || "Please push that button!"}</p>
      <div>
        <button id="startBtn" onClick={handleClick1}>
          Start Normal
        </button>
        <button id="startBtn1" onClick={handleClick2}>
          Start With setTimeout
        </button>
      </div>
      <p>Result: {time}</p>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
like image 709
Siyavash Hamdi Avatar asked Oct 16 '19 16:10

Siyavash Hamdi


People also ask

What happens when setTimeout is 0?

Calling setTimeout with a delay of 0 (zero) milliseconds doesn't execute the callback function after the given interval. The execution depends on the number of waiting tasks in the queue.

Is setTimeout accurate?

It's not supposed to be particularly accurate. There are a number of factors limiting how soon the browser can execute the code; quoting from MDN: In addition to "clamping", the timeout can also fire later when the page (or the OS/browser itself) is busy with other tasks.

What happens when if setTimeout () call with 0ms?

To explain: If you call setTimeout() with a time of 0 ms, the function you specify is not invoked right away. Instead, it is placed on a queue to be invoked “as soon as possible” after any currently pending event handlers finish running.


2 Answers

React batches renders that are queued up within an event callback, so all of your calls to setValue in the click handler result in a single render once your handler finishes.

However, I do not believe React batches renders from within a setTimeout call. So it is rendering after each call to setValue in your setTimeout handler.

See this issue: https://github.com/facebook/react/issues/14259

You should be able to make the setTimeout version faster by writing it like so:

const handleClick2 = () => {
  setTimeout(() => ReactDOM.unstable_batchedUpdates(() => startSample()));
}

If you have a bunch of async ajax responses coming back and want to apply them after they all arrive, then you might have code like this:

const [a, setA] = useState();
const [b, setB] = useState();
const [c, setC] = useState();
const [d, setD] = useState();

useEffect(() => {
  (async () => {
    const a = await fetchA();
    const b = await fetchB();
    const c = await fetchC();
    const d = await fetchD();

    // wrap all of the state updates in batchUpdates
    // so that we only get one render instead of 4
    ReactDOM.unstable_batchUpdates(() => {
      setA(a);
      setB(b);
      setC(c);
      setD(d);
    });
  })()
}), []);
like image 53
Brandon Avatar answered Nov 04 '22 13:11

Brandon


React currently will batch state updates if they're triggered from within a React-based event, like a button click or input change. It will not batch updates if they're triggered outside of a React event handler, like a setTimeout().

Consider the following

  const startSample = () => {
    const startTimeStamp = new Date().valueOf();
    const arr = []
    for (let i = 0; i < 5000; i++) {
      arr.push(i)
      // console.log(`test-${i}`);
    }
    setValue(arr)
    const endTimeStamp = new Date().valueOf();
    setTime(`${endTimeStamp - startTimeStamp}ms`);
  };

Now both times are about the same right? As pointed out by Brandon React doesn't seem to wait all udpates finishes before triggering a new render, resulting in five thousand renders instead of just one. So use an accumulator to perform the iterations and set the state just once

like image 45
Dupocas Avatar answered Nov 04 '22 14:11

Dupocas