проблема стека вызовов setTimeout - PullRequest
0 голосов
/ 14 октября 2019

Я узнал о стеке вызовов в JS и попробовал какой-то код самостоятельно, вот в чем проблема:

const timestamp = Date.now(); // get current timestamp
setTimeout(() => console.log(Date.now() - timestamp), 2000); // get difference
Array(10000).fill(0).forEach((item, i) => console.log(i ** 3)); // simulating long-running task

Проблема в том, что после запуска кода в браузере, это занимает гораздо больше времени, чем2000 мс для записи разницы в консоли, вероятно, 4000-6000 мс, но у меня 2000-2300 мс в моей консоли. Я узнал, что таймер setTimeout запускается после очистки стека вызовов, следуя этой логике, я должен получить 4000-5000 мс в моей консоли. Вопрос в том, почему я получаю 2000-2300 мс вместо 4000-6000?

1 Ответ

1 голос
/ 14 октября 2019

Я не знаю, сделать ли это комментарием или ответом. Я думаю, что это может быть связано с проблемой очистки буфера консоли. Может показаться, , что для завершения функции требуется более 5 секунд, потому что она печатает столько консольных строк, но на самом деле это занимает всего ~ 2 секунды, запускает таймаут и ~ 6 секунд, чтобы вы могли увидетьконечный результат всех утешительных. Попробуйте удалить console.log из forEach и посмотрите, можете ли вы по-прежнему вызывать эту проблему.

Вместо использования переменной «длительный процесс», которая на самом деле может не работать долго на всех компьютерах,давайте создадим пример, который определенно заблокирует поток выполнения на заданную продолжительность и посмотрим, получим ли мы тот же результат. Давайте также уберем возможность возникновения проблемы с консольным буфером.

Вот мой пример, написанный на node.js . Я не совсем уверен, как мы можем надежно блокировать поток выполнения в браузере на установленную продолжительность. Много усилий было направлено на предотвращение именно этого из-за его влияния на пользовательский опыт. Одной из идей может быть синхронный вызов AJAX, который истекает после браузера по умолчанию. В любом случае, используя узел, мы можем написать:

const {execSync} = require("child_process");

/**
 * Test stack/setTimeout interactions
 * @param {Number} [t1=5] Duration of setTimeout in seconds
 * @param {Number} [t2=5] System sleep duration in seconds
 */
function test(t1=5, t2=5) {
     // Get time at start
    const start = Date.now();

    // Schedule printing of delta
    setTimeout(() => {
        console.log(`Diff: ${Date.now() - start}`;
    }), t1 * 1000);

    // Synchronously sleep for a duration
    execSync(`sleep ${t2}`);
}

Вот моя выходная матрица:

test(1, 5); // Diff: 5021
test(5, 1); // Diff: 5002
test(3, 3); // Diff: 3018

Внутренняя функция вычисления дельты никогда не запускается, пока не закончится весь блок выполнения. Вот почему вы видите diff как больший из 2 аргументов (умноженный на 1000).


Один из примеров создания примера кросс-платформенной блокировки (это даже проще, чем мой) - использоватьwhile блок с Date.now(), как это сделано в этом вопросе: setTimeout поведение с кодом блокировки . Фактически, этот вопрос может даже дать некоторую дополнительную информацию о логике setTimeout с долго выполняющимися процессами:

console.log('Before wait');
setTimeout(function () { console.log('Yo!'); }, 1000);
var start = Date.now();
while (Date.now() < start + 3000) {}
console.log('After wait');

Имейте в виду, что вам следует избегать написания кода, который блокируетосновной поток выполнения JavaScript, , это всегда плохая идея . Если вам нужно использовать длительную логику, используйте WebWorkers .

...