Чому 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.