События ввода DOM или setTimeout/setInterval

У меня есть блок кода JavaScript, который запущен на моей странице; позвоните ему func1. Выполняется несколько миллисекунд. Пока этот код запущен, пользователь может щелкнуть, переместить мышь, ввести ввод с клавиатуры и т.д. У меня есть еще один блок кода func2, который я хочу запустить после того, как все эти события, связанные с очередностью ввода, будут разрешены. То есть, я хочу обеспечить порядок:

  • func1
  • Все обработчики, связанные с событиями ввода, которые произошли во время func1, выполнялись
  • func2

Мой вопрос: Является ли вызов setTimeout func2, 0 в конце func1 достаточным для обеспечения этого заказа во всех современных браузерах? Что делать, если эта строка появилась в начале func1 - какой порядок я должен ожидать в этом случае?

Пожалуйста, создайте резервные копии своих ответов либо с ссылками на соответствующие спецификации, либо с помощью тестовых примеров.

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

// time-consuming loop...
setTimeout func2, 0

то только после этого setTimeout будет запущен любой входной события (клики и т.д.), которые произошли во время цикла времени. (Чтобы проверить это, обратите внимание, что если вы удаляете, скажем, обратный вызов onclick сразу после цикла, требующего много времени, то щелчки, которые произошли во время цикла, не будут инициировать этот обратный вызов.) Таким образом, func2 сначала ставится в очередь и принимает старшинство.

Установка тайм-аута 1, похоже, обошла проблему в Chrome и Safari, но в Firefox я видел, как входные события разрешались после тайм-аутов до 80 (!). Таким образом, чисто основанный на времени подход явно не собирается делать то, что я хочу.

Нет, достаточно просто обернуть один setTimeout ... 0 внутри другого. (Я надеялся, что первый тайм-аут будет срабатывать после ввода событий в очереди, а второй будет срабатывать после того, как они разрешатся. Нет такой удачи.) Также не было добавлено третьего или четвертого уровня вложенности (см. Обновление 2 ниже).

Итак, если у кого-то есть способ достичь того, что я описал (кроме установки тайм-аута в 90+ миллисекунд), я был бы очень благодарен. Или это просто невозможно с текущей моделью событий JavaScript?

Здесь мой последний тестовый стенд JSFiddle: http://jsfiddle.net/EJNSu/7/

Обновление 2: Частичное обходное решение - вложить func2 внутри двух тайм-аутов, удалив все обработчики входных событий в первый тайм-аут. Однако у этого есть неудачный побочный эффект, вызвавший неспособность решить некоторые или даже все входные события, произошедшие во время func1. (Перейдите в http://jsfiddle.net/EJNSu/10/ и попробуйте быстро нажать ссылку несколько раз, чтобы наблюдать за этим поведением. Сколько кликов предупреждает вас о том, что у вас было? ) Так что это снова меня удивляет; Я бы не подумал, что вызов setTimeout func2, 0, где func2 устанавливает onclick в null, может помешать этому обратному вызову быть запущенным в ответ на щелчок, который произошел полторы секунды назад. Я хочу, чтобы все входные события срабатывали, но моя функция срабатывает после них.

Обновление 3:. Я отправил свой ответ ниже после игры с этим тестом, который освещает: http://jsfiddle.net/TrevorBurnham/uJxQB/

Переместите курсор мыши над полем (запуск 1-секундного цикла блокировки), затем щелкните несколько раз. После цикла все сделанные вами клики выходят: Верхний бокс click обработчик переворачивает его под другим полем, который затем получает следующий click и т.д. Тайм-аут, вызванный обратным вызовом mouseenter, последовательно не возникает после событий кликов, и время, которое требуется для событий кликов, сильно варьируется в браузерах даже на одном оборудовании и ОС. (Еще одна странная вещь, в которой появился этот эксперимент: иногда я получаю несколько событий jQuery mouseenter, даже когда я постоянно перемещаю курсор мыши в окно. Не уверен, что происходит там.)

Ответ 1

На этом этапе я готов сказать, что, к сожалению, нет решения этой проблемы, которая будет работать во всех браузерах в каждом сценарии каждый раз. Вкратце: если вы запустите функцию JavaScript, нет возможности надежно различать события ввода, которые пользователь запускал в течение этого времени, и те, которые пользователь запускал после этого. Это имеет интересные последствия для разработчиков JS, особенно тех, которые работают с интерактивными холстами.

Моя ментальная модель того, как работают события ввода JS, не соответствует значению. Я думал, что он пошел

  • Пользователь нажимает элемент DOM во время работы кода
  • Если этот элемент имеет обработчик события click, обратный вызов помещается в очередь
  • Когда весь код блокировки выполнен, обратный вызов запускается

Однако мои эксперименты, а также те, которые были внесены Владимиром Палантом (спасибо, Владимир), показывают, что правильная модель

  • Пользователь нажимает элемент DOM во время работы кода
  • Браузер фиксирует координаты и т.д. щелчка
  • Через некоторое время после выполнения кода блокировки браузер проверяет, какой элемент DOM находится в этих координатах, затем запускает обратный вызов (если есть)

Я говорю "через некоторое время после", потому что у разных браузеров для этого очень разные поведения: в Chrome для Mac я могу установить setTimeout func2, 0 в конце моего кода блокировки и ожидать func2 для запуска после clickbackback (которые запускаются только 1-3 мс после завершения кода блокировки); но в Firefox тайм-аут всегда разрешается первым, и обратные вызовы щелчка обычно происходят ~ 40 мс после завершения кода блокировки. Это поведение, по-видимому, выходит за рамки любой спецификации JS или DOM. Как сказал Джон Ресиг в своем классическом Как работают таймеры JavaScript:

Когда происходит асинхронное событие (например, щелчок мышью, запуск таймера или завершение XMLHttpRequest), он становится в очереди, чтобы быть выполненным позже (как это происходит на самом деле, это зависит от браузера от браузера, поэтому рассмотрите это как быть упрощением).

(Подчеркните мой.)

Итак, что это значит с практической точки зрения? Это не проблема, так как время выполнения кода блокировки приближается 0. Это означает, что эта проблема является еще одной причиной для того, чтобы помочь этому старым советам: Разбить ваши операции JS на небольшие куски, чтобы избежать блокировки потока.

Работники веб-сайтов, как предлагается бесполезный код, еще лучше, когда вы можете их использовать, но имейте в виду, что вы отказываетесь от совместимости с Internet Explorer и всеми основными мобильных браузеров.

Наконец, я надеюсь, что разработчики браузеров будут продвигаться вперед по стандартизации событий ввода в будущем. Это одна из многих причуд в этой области. Надеюсь, Chrome заложит путь к будущему: отличная изоляция потоков, низкая задержка событий и относительно последовательное поведение в очереди. Веб-разработчик может мечтать, не так ли?

Ответ 2

Если я правильно понимаю ваш вопрос, у вас есть долговременная функция, но вы не хотите блокировать интерфейс во время его работы? После выполнения длительной функции вы хотите запустить еще одну функцию?

Если это так, вместо использования тайм-аутов или интервалов, вы можете вместо этого использовать "Веб-рабочие" . Все современные браузеры, включая IE9, должны поддерживать веб-работников.

Я собрал страницу (не мог поместить ее на jsfiddle, так как веб-работники полагаются на внешний .js файл, который должен быть размещенный на том же самом происхождении).

Если вы нажмете A, B, C или D, сообщение будет зарегистрировано справа. Когда вы нажимаете Start, Web Worker начинает обработку в течение 3 секунд. Любые клики за эти 3 секунды будут немедленно зарегистрированы.

Важные части кода находятся здесь:

func1.js Код, который выполняется внутри веб-рабочего

onmessage = function (e) {
    var result,
    data = e.data, // get the data passed in when this worker was called
                   // data now contains the JS literal {theData: 'to be processed by func1'}
    startTime;
    // wait for a second
    startTime = (new Date).getTime();
    while ((new Date).getTime() - startTime < 1000) {
        continue;
    }
    result = 42;
    // return our result
    postMessage(result);
}

Код, вызывающий веб-исполнителя:

var worker = new Worker("func1.js");
// this is the callback which will fire when "func1.js" is done executing
worker.onmessage = function(event) {
    log('Func1 finished');
    func2();
};

worker.onerror = function(error) {
    throw error;
};

// send some data to be processed
log('Firing Func1');
worker.postMessage({theData: 'to be processed by func1'});

Ответ 3

Я думаю, что вы ошибаетесь в своих экспериментах. Одна из проблем, конечно, заключается в том, что вы сражаетесь с различными реализациями цикла сообщений здесь. Другой (тот, который вы не узнали, похоже) отличается от обработки двойного щелчка. Если вы дважды нажмете ссылку, вы не получите два события click в MSIE - это скорее одно событие click и событие dblclick (для вас, похоже, второй щелчок был "проглочен" ). Кажется, что все другие браузеры генерируют два события click и событие dblclick в этом сценарии. Поэтому вам нужно обрабатывать события dblclick.

По мере того как петли сообщений идут, Firefox должен быть проще всего обрабатывать. Из всего, что я знаю, Firefox добавляет сообщения в очередь, даже когда работает JavaScript-код. Таким образом, простого setTimeout(..., 0) достаточно для запуска кода после обработки сообщений. Вы должны воздерживаться от скрытия ссылки после выполнения func1(), однако в этот момент клики еще не обработаны, и они не будут вызывать обработчики событий на скрытом элементе. Обратите внимание, что даже нулевой тайм-аут сразу не добавляется в очередь, текущие версии Firefox имеют 4 миллисекунды как минимально возможное значение тайм-аута.

MSIE похожа на то, что вам нужно обрабатывать события dblclick, как я упоминал ранее. Опера, похоже, работает так же, но не нравится, если вы не вызываете event.preventDefault() (или возвращаете false из обработчика события, который по сути является тем же самым).

Однако Chrome, похоже, сначала добавляет тайм-аут в очередь и только после этого добавляет входящие сообщения. Вложенные два тайм-аута (с нулевым значением таймаута), похоже, выполняют эту работу здесь.

Единственный браузер, в котором я не могу надежно работать, - это Safari (версия 4.0 в Windows). Расписание сообщений кажется случайным там, похоже, что таймеры там выполняются в другом потоке и могут отправлять сообщения в очередь сообщений в случайные моменты времени. В конце концов вам, вероятно, придется признать, что ваш код не может быть прерван в первый раз, и пользователю может потребоваться дольше секунда.

Вот моя адаптация вашего кода: http://jsfiddle.net/KBFqn/7/

Ответ 4

Вы можете использовать dispatchEvent с настраиваемым именем события в конце вашей функции. Это не будет работать на IE, но все еще возможно; просто используйте fireEvent.

Взгляните на это:

http://jsfiddle.net/minitech/NsY9V/

Нажмите "Запустить длинный", нажмите на текстовое поле и введите его. Вуаля!

Ответ 5

Вы можете настроить проверку обработчиков событий, чтобы определить, установлен ли флаг func1; если так очередь func2, если она еще не поставлена ​​в очередь.

Это может быть либо элегантным, либо уродливым в зависимости от специализации func2. (На самом деле это, наверное, просто уродливо.) Если вы выберете этот подход, вам нужно каким-то образом перехватить события или, альтернативно, свою собственную функцию bindEvent(event,handler,...), которая обертывает обработчик и связывает обработанный обработчик.

Правильность этого подхода зависит от того, все события в течение func1 поставлены в очередь одновременно. Если это не так, вы можете либо сделать func2 idempotent, либо (в зависимости от семантики func2) положить уродливое "не может быть вызвано снова для N миллисекунд" блокировки на нем.

Ответ 6

просьба описать лучший сценарий.

Что вам нужно сделать

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

например, чтобы я показал, как эта работа

сначала зарегистрировать все подпрограммы async или sync второй обратный вызов регистра третий вызов регистра для подпрограмм с вашими параметрами четвертый брошенный процесс

в вашем случае необходимо добавить одну процедуру вызова, и эта процедура должна быть UoW действий пользователя. Теперь главная проблема заключается не в вызове подпрограммы и порядке выполнения, если не отслеживании изменений, сделанных пользователем

сначала зарегистрировать все подпрограммы async или sync второй обратный вызов регистра третий вызов регистратора в подпрограммы с вашим параметром --Зарегистрируйте свою первую процедуру --register BlockUi//возможно, чтобы не принимать больше изменений в представлении --register UiWriter//UoW изменения, сделанные пользователем --Зарегистрируйте свою последнюю рутину четвертый брошенный процесс

в реальном коде, который является фиктивной функцией вызова

function Should_Can_Serializer_calls() {
RegisterMethods (модель);
model.Queue.BeginUnitProcess();//очистить стек исполнения, другие model.Queue.AddEndMethod(SucessfullEnd);//callback to end model.AbstractCall( "func1", 1, "образование", 15 ");//установить рутину, как выполнить сначала model.AbstractCall(" BlockUi ");//отслеживать изменения и действия пользователя model.AbstractCall(" UiWork ");//отслеживать изменения и действия пользователя model.AbstractCall(" func2 "," ЗНАЧЕНИЕ");//установить вторую процедуру для выполнения model.Process();//вызов вызова }

Теперь методы должны быть асинхронны для этого, вы можете использовать эту библиотеку http://devedge-temp.mozilla.org/toolbox/examples/2003/CCallWrapper/index_en.html

Итак, что вы хотите сделать?