Чому setTimeout не скасовує мій цикл?


75

Я задався питанням, скільки разів whileоператор JavaScript (у консолі Chrome) може збільшувати змінну за мілісекунди, тому я швидко написав цей фрагмент безпосередньо в консоль:

var run = true, i = 0;
setTimeout(function(){ run = false; }, 1);
while(run){ i++; }

Проблема в тому, що вона працює вічно.
Чому це відбувається, і як я можу це вирішити?


27
Оскільки цикл while не дає часу процесора до часу очікування
— mplungjan

Може бути , це було б корисно прочитати: stackoverflow.com/questions/21463377 / ...
— jillro

4
Чому це питання так високо оцінюється?

6
@remyabel Я думаю, що це тому, що насправді це чудовий випадок для демонстрації того, як працює однопотоковий. У JS загальноприйняте припущення, що інтервал / час очікування негайно перериває потік управління та виконує, коли в деяких випадках - як зазначено вище - цього ніколи не станеться.
— MickMalone1983

2
Як продовження наведених нижче відповідей, це те, як ви зробите те, що очікували, використовуючи відкладену рекурсивну функцію:var run = true, i = 0; function loop() { i++; if (run) setTimeout(loop, 0); }; setTimeout(function() { run = false }, 1); loop();
— Ізката

Відповіді:


80

Це повертається до однопоточної природи JavaScript 1 . Що відбувається в основному це:

  1. Ваші змінні присвоєні.
  2. Ви плануєте встановити функцію run = false. Це планується запустити після запуску поточної функції (або будь-якої іншої активної в даний час).
  3. Ви маєте свій нескінченний цикл і залишаєтесь у поточній функції.
  4. Після вашого нескінченного циклу ( ніколи ) setTimeout()зворотний дзвінок буде виконаний і run=false.

Як бачите, setTimeout()підхід тут не спрацює. Ви можете обійти це, перевіривши час у whileстані, але це змінить ваші фактичні вимірювання.

1 Принаймні для більш практичних цілей ви можете бачити його як однопоточний. Насправді існує так званий "цикл подій". У цьому циклі всі функції потрапляють у чергу до їх виконання. Якщо ви ставите в чергу нову функцію, вона розміщується у відповідному положенні всередині цієї черги. Після завершення поточної функції движок приймає наступну функцію з черги (щодо таймінгу, як він введений, наприклад, by setTimeout()і виконує його.
В результаті в кожен момент часу виконується лише одна функція, що робить виконання гарним є багато винятків для подій, про які йдеться у посиланні нижче.


Для довідки:


3
Як @Sirko натякнув, для досягнення того ефекту, який ви шукали, збережіть поточну позначку часу у змінну, var start = (new Date()).getTime();Потім у whileоператорі відніміть цей час від поточного часу і перевірте, чи не виходить число менше задуманого затримки . while ((new Date()).getTime() - start < delay).
— марний код

1
Якщо все, що вам потрібно, це побачити, наскільки швидко Chrome запускає його, тоді встановіть цикл while для запуску приблизно на 10000 кроків. Встановіть змінну, наприклад час початку перед циклом, а потім поточний час після циклу, а потім порівняйте два.
— SyntaxLAMP

"Іноді" - це саме тоді, коли стек порожній.
— jillro

Це насправді не через однопоточну природу javascript. Навіть якщо це було б дуже дурним, реалізація все-таки може бути "перевірка повідомлень між кожною інструкцією", оскільки JS є інтерпретованою мовою. Це через реалізацію циклу подій, який викликається, коли стек порожній.
— jillro

@Guilro Щодо "іноді" - концепція називається іронією, і, я думаю, я уточнила виділеним "ніколи". Що стосується "повідомлення між" - кожен движок JS, який я знаю, в основному є однопоточним (крім робітників). Зверніться до stackoverflow.com/a/2734311/1169798 для більш детального аналізу. До речі, більшість сучасних двигунів повертаються до безпосередньої інтерпретації JS лише тоді, коли їхні вдосконалені можливості виходять з ладу.
— Сірко

24

JavaScript є однопотоковим, тому, поки ви знаходитесь у циклі, більше нічого не виконується.


2
Будьте обережні, коли називаєте JavaScript однопотоковим, оскільки він має можливість обробляти багатопотоковість. Однак стандартне виконання однопоточне.
— zzzzBov

19

Щоб зберегти справжню швидкість Chrome без необхідності постійно отримувати час для обчислення швидкості, ви можете спробувати цей код JS:

var start = new Date().getTime()
var i = 0
while(i<1000000)
{
    i++
}
var end = new Date().getTime()
var delta = end-start
var speed = i/delta
console.log(speed + " loops per millisecond")

4

Javascript однопотоковий, це означає, що він виконує лише одну інструкцію одночасно, послідовно.

Система подій, як і в багатьох інших мовах та бібліотеці, обробляється циклом подій. Цикл подій - це, в основному, цикл, який на кожній ітерації перевіряє наявність повідомлення в черзі та відправляє події.

У javascript (як у мові мов, що реалізують цей шаблон), цикл подій викликається, коли стек порожній, тобто, коли всі функції мають повернення, іншими словами, в кінці програмного коду.

Ваша "справжня" програма виглядає за кадром приблизно так:

var run = true, i = 0;
setTimeout(function(){ run = false; }, 1);
while(run){ i++; }

while(true) {
/*
 * check for new messages in the queue and dispatch
 * events if there are some
 */
  processEvents();
}

Тож повідомлення від годинника про те, що час очікування закінчився, ніколи не обробляється.

Докладніше про цикл подій на: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/EventLoop


Звичайно, це трохи складніше, перегляньте ось ці приклади: Чи гарантовано JavaScript буде однопоточним? ( tl; dr: У деяких механізмах браузера деякі зовнішні події не залежать від циклу подій і негайно запускаються, коли вони відбуваються, випереджаючи поточне завдання. Але це не так у випадку setTimeout, який просто додає повідомлення в чергу і ніколи не стріляє негайно.)


2

Цикл While не має доступу до setTimeout. У вас є код, для якого набори виконуються true, і тоді він ніколи не стане хибним.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.