Underscore: Предложение: _.debounce и _.throttle принимают дополнительный параметр для объединения аргументов.

Созданный на 22 сент. 2011  ·  11Комментарии  ·  Источник: jashkenas/underscore

Если я использую _.debounce() для создания функции debounce, а затем вызываю ее 3 раза подряд с 3 разными наборами аргументов, то (начиная с версии 1.1.7) обернутая функция полезной нагрузки, наконец, будет вызываться с аргументами, указанными в 3-й вызов - то есть первый и второй аргументы отбрасываются.

Хотя это часто допустимо (и что обычно делает устранение дребезга ключей, следовательно, разумное значение по умолчанию), я обнаружил, что хочу использовать debounce для накопления аргументов, например, у меня есть вызов AJAX, который может получить несколько ключей одновременно, поэтому я использую debounce для буферизации вверх на секунду, а затем выдать комбинированный запрос.

Поэтому я предлагаю, чтобы debounce принимал необязательный третий аргумент «объединения», который будет вызываться с двумя аргументами,

  • 1-й - это список накопленных до сих пор аргументов (возможно, неопределенный - т.е. нет списка при первом вызове)
  • второй - список аргументов для последнего вызова (возможно, пустой список)
    и возвращает новый список накопленных аргументов. При вызове функции полезной нагрузки накопленный список аргументов очищается.

Если для параметра комбинирования не передается значение, комбинирование по умолчанию сохраняет существующее поведение.
function(acc, newargs) { return newargs; }
но вы также можете решить использовать первый набор аргументов
function(acc, newargs) { return acc || newargs; }
или то, что я хочу сделать, это просто добавить все аргументы
function(acc,newargs) { return (acc || []).concat(newargs); }
и, конечно, другие могут захотеть сделать что-то более красивое

Для этого потребуется следующее изменение функции внутреннего ограничения.

  // Internal function used to implement `_.throttle` and `_.debounce`.
  var limit = function(func, wait, debounce, combine) {
    var timeout, allargs;
    return function() {
      var context = this;
      allargs = combine(allargs,  slice.call(arguments,0))
      var throttler = function() {
        timeout = null;
        var args = allargs;
        allargs = undefined;
        func.apply(context, args);
      };
      if (debounce) clearTimeout(timeout);
      if (debounce || !timeout) timeout = setTimeout(throttler, wait);
    };
  };

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

  _.debounce = function(func, wait, combine) {
    return limit(func, wait, true, combine || function(acc,newargs) { return newargs; });
  };

Соответствующая функция дросселя в настоящее время использует только первый набор аргументов (throttle фактически игнорирует вызовы, происходящие в течение миллисекунд ожидания первого вызова, и использует первый набор аргументов вызова, debounce эффективно игнорирует все, кроме последнего вызова в последовательности, происходящие в течение периода ожидания друг друга), поэтому я бы предложил ниже снова сохранить текущее поведение по умолчанию

  _.throttle = function(func, wait, combine) {
    return limit(func, wait, false, combine || function(acc,newargs) { return acc || newargs; });
  };

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

enhancement wontfix

Самый полезный комментарий

Хорошо, не для того, чтобы продолжать стучать, но на случай, если кто-то посмотрит на это позже и задастся вопросом, как сделать то же самое, я решил, что это был самый чистый способ без изменения самого debounce (я добавляю его к объекту _, другие могут предпочесть не к)

_.mixin({
  debounceReduce: function(func, wait, combine) {
    var allargs,
        context,
        wrapper = _.debounce(function() {
            var args = allargs;
            allargs = undefined;
            func.apply(context, args);
        }, wait);
        return function() {
            context = this;
            allargs = combine.apply(context, [allargs,  Array.prototype.slice.call(arguments,0)]);
            wrapper();
        };
    }
})

Это дает отмененную функцию, аргументы которой уменьшены с помощью функции объединения, поэтому, например,

  delayLog = _.debounceReduce(function() { console.log(arguments); }, 5000, 
                              function(acc,args) { return (acc || []).concat(args); });
  delayLog(3,4);
  delayLog(7,8,9);

через несколько секунд вызовет console.log с массивом [3,4,7,8,9]

Все 11 Комментарий

Комментируя мое собственное предложение, вызов comb() должен указывать тот же контекст, что и функция полезной нагрузки, поэтому

allargs = combine.apply(this, [allargs, slice.call(arguments,0)])

в случае, если аргументы должны получить доступ к объекту контекста....

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

Я думаю, что ваш подробный комментарий относится к другой проблеме (вероятно, № 170), поскольку проблема, поднятая этим запросом, по-прежнему актуальна для master.
До сих пор не существует простого способа, чтобы дебаунс или дроссель накапливал аргументы из объединяемых вызовов, и я все еще думаю, что это полезное дополнительное дополнение, которое оставляет поведение по умолчанию неизменным, когда необязательный аргумент объединения не указан.

Ах, ты прав. Накопление аргументов выходит за рамки Underscore — не стесняйтесь хранить накопленные данные в удобном месте вне функций _.throttle и _.debounce .

Жаль, я считаю debounce своего рода fold-left (reduce) по нескольким вызовам с тайм-аутом, следовательно, аккумулятор ... но это ваш выбор :)

Хорошо, не для того, чтобы продолжать стучать, но на случай, если кто-то посмотрит на это позже и задастся вопросом, как сделать то же самое, я решил, что это был самый чистый способ без изменения самого debounce (я добавляю его к объекту _, другие могут предпочесть не к)

_.mixin({
  debounceReduce: function(func, wait, combine) {
    var allargs,
        context,
        wrapper = _.debounce(function() {
            var args = allargs;
            allargs = undefined;
            func.apply(context, args);
        }, wait);
        return function() {
            context = this;
            allargs = combine.apply(context, [allargs,  Array.prototype.slice.call(arguments,0)]);
            wrapper();
        };
    }
})

Это дает отмененную функцию, аргументы которой уменьшены с помощью функции объединения, поэтому, например,

  delayLog = _.debounceReduce(function() { console.log(arguments); }, 5000, 
                              function(acc,args) { return (acc || []).concat(args); });
  delayLog(3,4);
  delayLog(7,8,9);

через несколько секунд вызовет console.log с массивом [3,4,7,8,9]

@schmerg — это выглядит чрезвычайно полезным. Хотели бы вы лицензировать этот код под лицензией MIT? («Да» будет достаточно!)

@markjaquith Конечно, да. С радостью...

Если кто-то приходит и хочет обновить / прокомментировать современную js-версию вышеизложенного:

_.mixin({
  debounceReduce(func, wait, combine) {
    let allArgs; // accumulator for args across calls

    // normally-debounced fn that we will call later with the accumulated args
    const wrapper = _.debounce(() => func(allArgs), wait);

    // what we actually return is this function which will really just add the new args to
    // allArgs using the combine fn
    return (...args) => {
      allArgs = combine(allArgs,  [...args]);
      wrapper();
    };
  },
});

@kmannislands Эй, ваша версия не сбрасывает allArgs внутри wrapper() , поэтому последующие вызовы func() получают исторические пакеты аргументов, а также текущий пакет.

Разве не должно быть:

const wrapper = _.debounce(() => {
    const args = allArgs;
    allArgs = undefined;
    func(args);
}, wait);

@markjaquith +1

Также func( args ) отличается от оригинальной версии, в которой используется func.apply(context, args); .
В первом случае args используется как есть в целевом func() , тогда как в более позднем _(исходном коде)_ вам нужно использовать либо arguments в обычной функции или ( ...args ) в функции жирной стрелки es6.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги