Как написать службу debounce в AngularJS

Библиотека подчёркивания предоставляет функцию debounce, которая предотвращает множественные вызовы функции в течение заданного периода времени. В их версии используется setTimeout.

Как мы можем сделать это в чистом коде AngularJS?

Кроме того, можно ли использовать стиль $q promises для извлечения возвращаемого значения из вызываемой функции после периода debounce?

Ответ 1

Вот рабочий пример такой службы: http://plnkr.co/edit/fJwRER?p=preview. Он создает отложенный объект $q, который будет разрешен при окончательном вызове функции debounced.

Каждый раз, когда функция debounce называется обещанием следующего вызова внутренней функции, возвращается.

// Create an AngularJS service called debounce
app.factory('debounce', ['$timeout','$q', function($timeout, $q) {
  // The service is actually this function, which we call with the func
  // that should be debounced and how long to wait in between calls
  return function debounce(func, wait, immediate) {
    var timeout;
    // Create a deferred object that will be resolved when we need to
    // actually call the func
    var deferred = $q.defer();
    return function() {
      var context = this, args = arguments;
      var later = function() {
        timeout = null;
        if(!immediate) {
          deferred.resolve(func.apply(context, args));
          deferred = $q.defer();
        }
      };
      var callNow = immediate && !timeout;
      if ( timeout ) {
        $timeout.cancel(timeout);
      }
      timeout = $timeout(later, wait);
      if (callNow) {
        deferred.resolve(func.apply(context,args));
        deferred = $q.defer();
      }
      return deferred.promise;
    };
  };
}]);

Вы получаете возвращаемое значение из функции debounced, используя метод then по обещанию.

$scope.addMsg = function(msg) {
    console.log('addMsg called with', msg);
    return msg;
};

$scope.addMsgDebounced = debounce($scope.addMsg, 2000, false);

$scope.logReturn = function(msg) {
    console.log('logReturn called with', msg);
    var promise = $scope.addMsgDebounced(msg);
    promise.then(function(msg) {
        console.log('Promise resolved with', msg);
    });
};

Если вы вызовете logReturn несколько раз подряд, вы увидите, что вызов logReturn занесен в систему снова и снова, но зарегистрирован только один addMsg.

Ответ 2

Angular 1.3 имеет стандартное отклонение

Стоит упомянуть, что debounce встроен с Angular 1.3. Как и следовало ожидать, он реализован как директива. Вы можете сделать это:

<input ng-model='address' ng-model-options="{ debounce: 500 }" />

Атрибут $scope.address не обновляется до 500 мс после последнего нажатия клавиши.

Если вам нужно больше управления

Если вы хотите больше детализации, вы можете установить разные времена отказов для разных событий:

<input ng-model='person.address' ng-model-options="{ updateOn: 'default blur', debounce: {'default': 500, 'blur': 0} }" />

Здесь, например, у нас есть откат на 500 мкс за нажатие клавиши и отсутствие отладки для размытия.

Документация

Прочитайте документацию здесь: https://docs.angularjs.org/api/ng/directive/ngModelOptions

Ответ 3

Поскольку я написал комментарии выше, у меня немного изменилось сердце.

Короткий ответ: вам не нужно отказываться от функций, возвращающих значения.

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

Ответ 4

Пит БД дал хорошее начало службе debounce, однако я вижу две проблемы:

  • возвращает, когда вы должны отправить обратный вызов work(), который использует закрытие javascript, если вам нужно изменить состояние в вызывающем.
  • timeout variable - не является ли эта переменная таймаутом проблемой? тайм-аут [] может быть? представьте 2 директивы, использующие debounce - signalr, валидатор формы ввода, Я считаю, что подход factory сломается.

То, что я сейчас использую:

Я изменил factory на службу, поэтому каждая директива получает НОВЫЙ экземпляр debounce aka нового экземпляра переменной тайм-аута. - Я не столкнулся с ситуацией, когда 1 директиве потребуется тайм-аут для таймаута [].

.service('reactService', ['$timeout', '$q', function ($timeout, $q) {
    this.Debounce = function () {
        var timeout;

        this.Invoke = function (func, wait, immediate) {
            var context = this, args = arguments;
            var later = function () {
                timeout = null;
                if (!immediate) {
                    func.apply(context, args);
                }
            };
            var callNow = immediate && !timeout;
            if (timeout) {
                $timeout.cancel(timeout);
            }
            timeout = $timeout(later, wait);
            if (callNow) {
                func.apply(context, args);
            }
        };
        return this;
    }
}]);

в моем удаленном валидаторе angularjs

    .directive('remoteValidator', ['$http', 'reactService', function ($http, reactService) {
        return {
            require: 'ngModel',
            link: function (scope, elm, attrs, ctrl) {
                var newDebounce = new reactService.Debounce();

                var work = function(){
//....
                };

                elm.on('blur keyup change', function () {
                   newDebounce.Invoke(function(){ scope.$apply(work); }, 1000, false);
                });
            }
        };
    }])

Ответ 5

Существует хорошая реализация как службы debounce, так и директивы, которая может работать с любой ng-моделью по адресу: https://github.com/shahata/angular-debounce

Или просто установите его, используя:

bower install ng-debounce

Ответ 6

https://github.com/capaj/ng-tools/blob/master/src/debounce.js

использование:

app.directive('autosavable', function(debounce) {
    return {
        restrict : 'A',
        require : '?ngModel',
        link : function(scope, element, attrs, ngModel) {
            var debounced = debounce(function() {
                scope.$broadcast('autoSave');
            }, 5000, false);

            element.bind('keypress', function(e) {
                debounced();
            });
        }
    };
});