Отложите запуск функции на n секунд, затем запустите ее один раз. (2минутный вопрос)

TL; DR. У меня есть функция, которая запускается на конце панорамирования карты открытых слоев. Не хочу, чтобы он горел постоянно.


У меня есть функция, которая запускается в конце панорамирования карты. Я хочу, чтобы он не запускал функцию, пока, скажем, через 3 секунды после завершения кастрюли. Хотя я не хочу ставить функцию в очередь на запуск 10 или около того раз, как это делает сейчас setTimeout.

Как я могу отложить запуск функции на n секунд, а затем запустить ее только один раз, независимо от того, сколько раз она вызывалась?

    map.events.register("moveend", map, function() {
      setTimeout(SetLocation, 5000);
    });

Moveend:

moveend - triggered after a drag, pan, or zoom completes

Код выше даже с использованием setTimeout (func, delay); по-прежнему срабатывает несколько раз при запуске. Как я могу предотвратить это?



person Sphvn    schedule 24.05.2010    source источник


Ответы (4)


Что ж, отвечая вашим требованиям, вы можете создать простую оболочку функции:

var executeOnce = (function (fn, delay) {
  var executed = false;
  return function (/* args */) {
    var args = arguments;
    if (!executed) {
      setTimeout(function () {
        fn.apply(null, args); // preserve arguments
      }, delay);
      executed = true;
    }
  };
});

Примеры использования:

С вашим кодом:

map.events.register("moveend", map, executeOnce(SetLocation, 5000));

Другое использование:

var wrappedFn = executeOnce(function (a, b) {
  alert(a + ' ' + b);
}, 3000);

wrappedFn('hello', 'world');
wrappedFn('foo', 'bar'); // this won't be executed...

Обернутая функция будет отложена на указанное время и будет выполняться только один раз.

person Christian C. Salvadó    schedule 24.05.2010
comment
В настоящее время функция не запускается. Пробуем именно то, что вы писали выше. спасибо за быстрый ответ :) / Edit: он стреляет, но когда он стреляет, он стреляет примерно 50-60 раз за раз? - person Sphvn; 24.05.2010
comment
Он по-прежнему вызывает многократное срабатывание функции SetLocation в точном соответствии с приведенным выше примером. - person Sphvn; 24.05.2010
comment
Одзаки, это странно ... ты уверен, что регистрируешь событие moveend только один раз? - person Christian C. Salvadó; 24.05.2010
comment
См. Выше для (moveend), но да. И даже если бы я одновременно выполнял масштабирование / панорамирование / перетаскивание, он должен срабатывать не более 3, а не менее 10 или около того. Даже если он запустит 3, он все равно должен остановить setTimeout с самого начала, верно? - person Sphvn; 24.05.2010
comment
@CMS: почему вы заключили первую функцию в ()? Я видел это раньше, чтобы работать в глобальной области, но в вашем случае ...? - person Warty; 24.05.2010
comment
@ItzWarty, сначала, когда я начал писать функцию, я думал вызвать ее немедленно, например var foo = (function () {...})();, в этих случаях парены добавляют удобочитаемость, но в этом случае Не вижу пользы ... - person Christian C. Salvadó; 24.05.2010
comment
@Ozaki, не могли бы вы создать рабочий пример, демонстрирующий нежелательное поведение, и поместить его где-нибудь, например, в jsbin.com в чтобы помочь вам лучше? - person Christian C. Salvadó; 24.05.2010
comment
Вероятно, происходит то, что map.events.register("moveend", map, executeOnce(SetLocation, 5000)); вызывается несколько раз. - person Gabe; 24.05.2010
comment
Я бы сказал, что если он вызывается несколько раз, есть способ предотвратить его и запустить только один раз. @CMS Файл, с которым я работаю, немного велик. Все, что я могу прикрепить, это эту функцию, поскольку это единственная ссылка на функцию, а moveend - это ссылка на OpenLayers. - person Sphvn; 24.05.2010
comment
@Ozaki - Только что проверил API OpenLayers. moveend в идеале должен срабатывать только один раз. Возможно, было бы лучше пройтись по коду в отладчике, чтобы выяснить, почему он вызывается несколько раз, а не исправлять его для выполнения только один раз, поскольку в дальнейшем это, скорее всего, вызовет больше проблем. См. Эту небольшую настройку на jsfiddle для moveend - jsfiddle.net/MFMaz. - person Anurag; 24.05.2010
comment
Я получаю звонок только один раз. Отладка в firebug МОЖЕТ БЫТЬ дважды, если я увеличиваю и панорамирую одновременно. : S Но когда я запускаю его, а не отлаживаю, он срабатывает несколько раз: S это не имеет смысла прямо сейчас. - person Sphvn; 24.05.2010
comment
Это кажется странным. Я предполагаю, что обработчики событий регистрируются несколько раз, что-то вроде jsfiddle.net/MFMaz/1 - где новый обработчик прикрепляется каждые 3 секунды. Обработчик moveend должен быть зарегистрирован только один раз. - person Anurag; 24.05.2010
comment
Да, глядя на это сейчас. OL, кажется, имеет приоритет над загрузкой по сравнению с таймаутом и т. Д. Таким образом, задержка увеличивается, он заканчивает загрузку и запускает их все сразу. Я не уверен, но, судя по всему, это именно то, что он делает - person Sphvn; 24.05.2010
comment
Получилось, теперь было много столкновений с другими вещами, которые блокировались вызовами OL / JSON. В комплекте всего понемногу, так что спасибо всем за помощь ^^ - person Sphvn; 24.05.2010

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

Я предполагаю, что в вашем случае происходит следующее:

(mouse move triggers callback)
setTimeout (1st)
(mouse move triggers callback)
setTimeout (2nd)
...
callback from 1st setTimeout is called
callback from 2nd setTimeout is called
...

Однако если вы используете clearTimeout, у вас будет:

(mouse move triggers callback)
setTimeout (1st)
(mouse move triggers callback)
clearTimeout (1st)
setTimeout (2nd)
...
callback from last setTimeout is called

Чтобы обновить предоставленный вами код JavaScript:

var delayedSetLocationId = -1;
...
map.events.register("moveend", map, function() {
    if (delayedSetLocationId >= 0) {
        clearTimeout(delayedSetLocationId);
    }
    delayedSetLocationId = setTimeout(SetLocation, 5000);
}); 
...
function SetLocation(...) {
    delayedSetLocationId = -1; // setTimeout fired, we can't cancel it now :)
    ...
}
person Milan Gardian    schedule 24.05.2010
comment
Используя код, который вы предоставили выше. У него хорошая задержка 10-20сек. Потом отстреливает целую кучу. Единственное, о чем я могу думать сейчас, это то, что он стоит в очереди, ожидая, пока карта OL закончит загрузку всего, что у нее есть, из очереди. - person Sphvn; 24.05.2010
comment
Я не уверен, что понимаю - если вы установите Timeout на 5000 миллисекунд, как у вас может быть задержка в 10-20 секунд? Функция SetLocation должна вызываться ровно через 5 секунд после последнего перехвата события moveend. Я бы порекомендовал: а) добавить оператор трассировки в обработчик события moveend (доступный для просмотра из инструментов разработчика IE / консоли Firebug / Chrome и т. Д.) И выяснить, как часто это событие запускается; б) установите точку останова в функции SetLocation и посмотрите на стек вызовов, чтобы выяснить, кто ее вызывает. - person Milan Gardian; 24.05.2010
comment
Еще одна вещь - вы можете попробовать отменить всплытие и обработку события moveend, чтобы ваш обработчик указывал, что вы обратили внимание и использовали событие. - person Milan Gardian; 24.05.2010

Именно для этого и предназначен setTimeout. Если setTimeout вызывает функцию 10 раз, что-то не так с вашим кодом, который вы не публиковали.

Также имейте в виду, что setTimeout не остановит скрипт.

person Daniel    schedule 24.05.2010

Я написал об этом небольшой пост. Это очень похоже на то, что предложила CMS.

Фрагмент кода выглядит так:

var delayonetimeaction = {

    oneTimeActions: {},

    /***
    ** Will wait the supplied "delay" until executing 
    ** the supplied "action" (function). 
    ** If called a second time before the with the 
    ** same id, the first call will be ignored and 
    ** instead the second will be used - that is the 
    ** second action will be executed after the second 
    ** supplied delay, not the first.
    ***/
    bind: function (delay, id, action) {

        // is there already a timer? clear if if there is
        if (this.oneTimeActions[id]) clearTimeout(this.oneTimeActions[id]);

        // set a new timer to execute delay milliseconds from last call
        this.oneTimeActions[id] = setTimeout(function () {
            action();
        }, delay);

    },

};

http://sds-digital.co.uk/post/2015/04/21/js-delayed-one-time-action.aspx.

person Daniel - SDS Group    schedule 21.04.2015