Как стать автором
Обновить

Комментарии 41

хорошая статья. Читал с удовольствием!
На JS не пишу, но читать было очень интересно — отличная статья! )
Очень полезно для тех, кто работает с JS. Когда я про этот цикл event loop услышал в лекции Крокфорда в 2010, это было доступно только на английском, и вот наконец-то стало массово появляться и у нас. Спасибо за труд!
Спасибо автору!
если setTimeout или setInterval был передано значение таймаута меньше 4 миллисекунд, вместо него использует 4 мс ровно

Уверены?
Первая ссылка — спека w3c:
> If timeout is less than 4, then increase timeout to 4.
Пример:
var i=0,
    t=setInterval(function(){i++},1);
setTimeout(function(){
    clearInterval(t);
    console.log(i) //252
  },1000);
В firefox эта цифра зависит, от того фоновая вкладка или нет:
10 ms — если активна
1000ms — если нет

Тут подробности:
Скрытый текст
Minimum delay and timeout nesting

Historically browsers implement setTimeout() «clamping»: successive setTimeout() calls with delay smaller than the «minimum delay» limit are forced to use at least the minimum delay. The minimum delay, DOM_MIN_TIMEOUT_VALUE, is 4 ms (stored in a preference in Firefox: dom.min_timeout_value), with a DOM_CLAMP_TIMEOUT_NESTING_LEVEL of 5ms.

In fact, 4ms is specified by the HTML5 spec and is consistent across browsers released in 2010 and onward. Prior to (Firefox 5.0 / Thunderbird 5.0 / SeaMonkey 2.2), the minimum timeout value for nested timeouts was 10 ms.

In addition to «clamping», the timeout can also fire later when the page (or the OS/browser itself) is busy with other tasks.

To implement a 0 ms timeout in a modern browser you can use window.postMessage() as described here.
Inactive tabs

In (Firefox 5.0 / Thunderbird 5.0 / SeaMonkey 2.2) and Chrome 11, timeouts are clamped to firing no more often than once per second (1000ms) in inactive tabs; see bug 633421 for more information about this in Mozilla or crbug.com/66078 for details about this in Chrome.

Это как должно быть. А вообще — все зависит от реализации браузера, тык.
Google Chrome 26.0.1410.65:
console.log(+new Date()); setTimeout(function(){console.log(+new Date())},1)
1367181447810
40745
1367181447812

Прыгает 2-4 мс.
А за что минусы? За то, что Хром у меня показал две миллисекунды вместо каноничных четырёх? :)
методика тестирования взята с потолка — правильно нужно делать тестами и с большим числом N измерений для достоверности.
Для достоверности — это если замерять среднее значение интервала.
А тут выводится таймстэмп, ставится таймер на 1 миллисекунду и снова выводится таймстэмп — получается разница в две миллисекунды вместо ожидаемых четырёх при первой же итерации. Т.е. на четыре миллисекунды полагаться нельзя. Я бы понял, если бы при первой итерации было больше 4 миллисекунд — всякие там задержки на интерпретацию команд, вывод в консоль и т.п. Но получилось меньше.
Впрочем, не представляю, где могут пригодиться такие короткие интервалы с такой точностью, а если даже и нужно ждать именно четыре миллисекунды, а не две, то это можно будет указать в параметрах.
Просто захотелось проверить — благо, консоль всегда под рукой :)
Короткие интервалы пригодятся, если надо обработать кучу данных, не повесив при этом браузер – разбить обработку на части и вызывать очередную итерацию через эти самые интервалы.
Например, как тут: http://alljs.ru/articles/timeout/fast-settimeout.
Имелось ввиду как-то так:

var tt = [];
var date1 = new Date();
var i = 0;
timer = function() {
  setTimeout(function() {
    i++;
    date2 = new Date();
    if (i < 100) {
      tt.push(date2 - date1);
      date1 = date2;
      timer();
    } else {
      console.log(tt);
    }
  }, 1)
};
timer();
// [2, 1, 2, 1, 4, 5, 5, 4, 4, 5, 4, 4, 4, 5, 4, 4, 4, 5, 4, 4, 4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 4, 5, 4, 4, 5, 4, 4, 5, 4, 5, 4, 4, 4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 4, 5, 4, 4, 5, 4, 4, 5, 4, 5, 4, 4, 4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 4, 5, 4, 4, 5, 4]
Интересно, почему в начале 1-2…
Хитрая оптимизация, пока интерпретатор не поймёт, что его обманывают, и надо бы делать паузу в 4 мс, как полагается по стандартам?
Файрфокс я пропустил, виноват, там такого ограничения действительно нет. Может быть, пока что нет, а может быть, и не собираются делать. Ну, и есть, разумеется, браузеры, навроде IE6, которые старше, чем WHATWG, и ни про какие 4 мс не слышали.

У Ильи Кантора есть страничка, где это поведение разбирается гораздо подробнее и в тестах.

В любом случае общее предупреждение «осторожно, оно может длиться намного дольше, чем ожидалось» остается в силе.
Выше привел ссылку, данные не первой свежести, но, вроде, пока соответствуют:
Opera 11 (Win): 2мс
Chrome 10-11 (Win): 4мс
Firefox (Win): 10мс
Safari, OS X 10.6.6: 10мс
iPhone: 10мс
IE 8: 15.6мс

В IE10 и на ноде есть еще такая веселая штука, как setImmediate.
Из статьи легко можно понять — что все современные движки JavaScript страдают одним недостатком — они однопоточные. Хотя нет — далеко не все — есть Rhino (по сути JIT транслятор JavaScript в Java)
А вот судьбе node.js не позавидую (сам на нем пишу — поэтому знаю что говорю) — разработчики V8 еще не скоро пофиксят проблемы в x64 и создание/управление Scope
P.S. а сейчас просто жду — может на KickStarter кто-нибудь выдвинет проект по реализации JS движка поверх Erlang — вот тогда будет действительно хорошо — особенно в многопоточности.
Сколько копий уже сломали — однопоточность в данном случае не недостаток, а достоинство. Надо параллельную обработку — запускай еще одну копию движка. А сделать многопоточность пойдут те же проблемы с синхронизацией, блокировками и т.д. И причем тут Erlang? Там совсем другой подход. И если поверх него городить JS то будет действительно плохо — особенно с производительностью.
Хоть мой коммент заминусовали — однако…
Если Вы писали хоть раз на Erlang распределенные приложения — то поймете как там легко решается проблема с блокировками и доступу к памяти.
На счет cluster — это модуль увы достаточно сырой, а его API нестабилен.
На счет производительности Вы лукавите — сравните node.js в пули из cluster на общих сокетах — против Erlang, это и будет ответом.
Ну да легко решается, но если вы реализуете интерпретатор JavaScript(по спецификации) на Erlang вы эту проблему в JavaScript не решите, а в производительности потеряете 100%. Если это будет не по спецификации — то это будет уже не JavaScript.
Про cluster я вообще ни словом не упоминал. Да и он тоже позволяет запускать параллельную обработку. Есть и другие способы.
Если так нравится Erlang и все на нем легко и просто, почему бы просто на нем не писать? Зачем там JavaScript?
Erlang хорош — но если проект разрабатывается не одним человеком (как правило так и есть), или будет иметь длинный жизненный цикл, или передан позже на утсорс другому человеку — то выбор тут невелик — или PHP или node.js на другое решение вряд-ли согласиться основной заказчик проекта.
Вообще-то для Erlang это решается транзакционными присваиваниями, по аналогии с теми что используются в базах данных.
Учитывая спецификацию — то смотря какую, если Вы решите повернуть сильно в сторону полиморфизма на ECMAScript Harmony — то возможно, а в остальном — Erlang быстрее чем Java, а для Java есть Rhino который очень даже хорош.
Без модуля cluster добиться какой-либо многопоточности проблематично — хотя конечно можно делать spawn процесса и своими силами — но это костыли скорее.
С++ быстрее чем Java и Erlang и для С++ о чудо! есть NodeJS :-)
Это я к чему: 3 раз намекаю — не факт что реализация JS на Erlang будет быстрее чего-либо.
Вы видно не совсем меня понимаете — не нужно путать язык и реализацию. Node.js — это V8 на стероидах.
С++ реализацию естественно она не обгонит — глупо бы было утверждать обратное — но вот в многопоточных задачах думаю Erlang победит (это уже много раз проверялось), и в каких-то моментах обойдет Java/Rhino — поскольку JVM тяжелее чем BEAM или HiPE.
Я не понял, где я путал язык и реализацию? Ни NodeJS, ни V8 это не язык.
Erlang может и победит в специфичных многопоточных задачах, а реализация JS на нем не факт. (4й раз, прошу заметить!).
На С++ есть куча реализаций JS. Это не делает их автоматически супербыстрыми, даже на простых задачах. Они все очень разные.
JS по природе не многопоточен. Зачем в него это пихать? Если использовать фишки Erlang в JS — изоляцию процессов, неизменяемость переменных, а только так можно добиться чудесного эффекта — это будет уже не JS. И писать на нем будет так же сложно как на Elang и ваша мечта об аутсорсе не сбудется.

Тоже самое можно сказать например и про Lua (тоже однопоточная модель) — однако github.com/rvirding/luerl (очень даже хорошее решение)
Есть memcached подобная реализация хранилища для Erlang — вот и хранилище разделяемых данных.
Естественно что не факт — что реализация на Erlang будет самая быстрая — однако как минимум будет иметь 3 основных преимущества которые в других языках или отсутствуют или осложнены — (1) fault-tolerance (2) виртуальная многопоточность (lightweight-threads) (3) multi-node IPC — что для серверных решений самое то.
Там целый список ограничений. «Хорошее решение» для чего?
Первые два согласен, причем первое вытекает из второго. Но lightweight-threads придется осваивать отдельно — аутсорс не получится.
А multi-node IPC вообще нигде не проблема. На вскидку — ZMQ и вперед.
Как встраиваемый язык — Lua для этого и сделан — позволяет легко управлять тяжелой логикой на erlang.
Вообще-то легковестный нити можно положить поверх setTimeout/setInterval/process.nextTick — что даст хорошую совместимость с legacy кодом.
По поводу multi-node IPC Я буду очень рад если Вы сможете показать как сделать легко такую же бесшовную реализацию как и в Erlang (в т.ч. автовыбор порта, сериализация объектов, переподключение, удаленная консоль) — ну и самая вкусная фишка Erlang — это полная связь между IPC и fault-tolerance.
ZMQ — это больше обмен сообщениями, а термин IPC в себя включает НЕ только обмен сообщениями на низком уровне, но и ряд других сервисов.
Насчет легковесных нитей ничего не понял. Зачем их ложить поверх setTimeout?
Я не собираюсь повторять Erlang, зачем мне что-то показывать?
Термин IPC включает в себя Inter Process Communication. Обмен сообщениями вполне себе коммуникация. Что он там включает в реализации Erlang совершенно не важно для самого термина. Переподключение, например, в некоторых реализациях вообще может не иметь смысла.
Делал свой Event Loop в приложении использующем MS Active Scripting.Поскольку были планы использовать сторонние JS библиотеки то пришлось реализовывать setTimeout setInterval.
Делал давно пошел посмотреть, как у меня там устроено — нашел ошибку. А всегда считал что правильно — работало вроде без проблем.
Почитать действительно было интересно. А как тогда правильно следить за изменениями в DOM?
Присоединяюсь к вопросу. (Ветки с ответами более заметны ^_^)
Например, ребята из Mozilla рекомендуют использовать Mutation Observers, если есть возможность.
Очень хорошо текст с примерами реализации сочетается. Спасибо за статью.

Вот только хотелось бы поподробнее про WebWorkers узнать. Как будут себя вести таймеры внутри WebWorker'а и таймеры в основном скрипте и т.д.
К сожалению по опыту скажу — что зависит от версии движка.
Отзывчивость это responsiveness. Responsibility = ответственность.
Отличная статья, интересно.

А этот цикл заставить выполниться никак нельзя? Было бы очень удобно для ожидания асинхронных операций.
Было бы очень удобно для ожидания асинхронных операций.
Было бы очень удобно, если бы yield умел не только (?) Firefox.
Удобство, впрочем, то еще. :)
Материал и его подача очень понравились.
Очень бы хотел увидеть от вас в дальнейшем ещё статьи про неочевидные вещи в js.
Поправьте опечатку в примере кода для setInterval:

setInterval(function(){
    console.log(+new Date());
}, 1000);
setTimeout(function(){
    var endDate = +new Date() + 2000; // !!! 2000
    while (+new Date() < endDate){
        // busy loop for 10 ms <<<-------------- 2000, а не 10
    }
}, 1500);
Большое спасибо за статью. Очень понравилась подача и тема очень интересная.
Подскажите, какими источниками пользовались при подготовке? Заранее спасибо.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий