Обратные вызовы setTimeout и события нажатия клавиш не следуют за FIFO в очереди событий? - PullRequest
0 голосов
/ 28 мая 2019

Я провожу эксперимент, чтобы лучше понять очередь событий JavaScript.

У меня дорогая операция, которая ничего не делает, которую я просто использую, чтобы занять основной поток.Это длится около 400 мс.

Затем у меня есть вход с прослушивателем события нажатия клавиши и обработчиком onChange, который выполняет эту дорогостоящую операцию всякий раз, когда на входе нажимается клавиша.Но перед запуском дорогостоящей операции он устанавливает setTimeout с задержкой 0 с некоторым обратным вызовом, который я мог отследить.

Я быстро нажал две клавиши подряд.Проверяя профиль производительности, я увидел, что второе нажатие клавиши регистрируется примерно через 150 мс после того, как первый обработчик нажатия клавиш начал выполнение.Это означает, что обратный вызов setTimeout с задержкой 0 должен завершить таймер и поместить обратный вызов в очередь событий.Событие нажатия клавиши также должно поместить свой обратный вызов обработчика в очередь, потому что основной поток все еще занят первой дорогой операцией, когда было зарегистрировано второе нажатие клавиши.

Таким образом, вы ожидаете, что, если обратный вызов времени ожидания был помещенсначала в очередь событий до 2-го события нажатия клавиши, что обратный вызов тайм-аута будет запущен первым, как только основной поток завершит дорогостоящую операцию, в соответствии с принципом очереди задач First In First Out.

Это не тот случай.Сначала происходит событие нажатия клавиши ...

Я пытаюсь понять, почему.Я предполагаю, что не существует только одной очереди событий.Пользовательские события, такие как нажатия клавиш, имеют отдельную очередь от событий таймера, и механизм JavaScript отдает приоритет пользовательским событиям над таймерами.

Вот кодовый ящик с этим сценарием, который реплицируется, так что вы можете проверить результаты самостоятельно.Код также написан ниже: https://codesandbox.io/s/r6vzp1qyq

Как вы думаете, что происходит, ребята?

function App() {
  const [timeoutTimestamps, setTimeoutTimestamp] = useState([]);
  const [keypressTimeStamps, setKeypressTimeStamp] = useState([]);

  //This expensive operation takes about 400 ms.
  function expensiveOperation() {
    const arr = new Array(30000000);
    arr.map(() => {
      arr.forEach(() => {
        arr.forEach(() => {});
      });
    });
  }

  function timerCallback() {
    const newTimestamp = Date.now();
    setTimeoutTimestamp(prevState => [...prevState, newTimestamp]);
  }

  function onKeypress() {
    const newTimestamp = Date.now();
    setTimeout(timerCallback, 0);
    expensiveOperation();
    setKeypressTimeStamp(prevState => [...prevState, newTimestamp]);
  }

  return (
    <div className="App">
      <input onChange={onKeypress} />
      <div className="container">
        <div className="time-stamps">
          <h3>Keypress TimeStamps</h3>
          {keypressTimeStamps.map(time => (
            <span>{time - keypressTimeStamps[0]}</span>
          ))}
        </div>

        <div className="time-stamps">
          <h3>Timeout TimeStamps</h3>
          {timeoutTimestamps.map(time => (
            <span>{time - keypressTimeStamps[0]}</span>
          ))}
        </div>
      </div>
    </div>
  );
}

Этот код отображает следующие метки времени: enter image description here

Вы видите, что сначала срабатывает обратный вызов 2-го нажатия клавиш, затем срабатывает обратный вызов 2-го тайм-аута.

...