49

underscore 库提供了一个 debounce 函数,可以防止在设定的时间段内多次调用一个函数。他们的版本使用了 setTimeout。

我们如何在纯 AngularJS 代码中做到这一点?

此外,我们是否可以使用 $q 风格的 promise 在去抖动期后从被调用函数中检索返回值?

4

7 回答 7

93

这是此类服务的一个工作示例:http ://plnkr.co/edit/fJwRER?p=preview 。它创建了一个$q延迟对象,当最终调用去抖动函数时,该对象将被解析。

每次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;
    };
  };
}]);

您可以使用 promise 上的 then 方法从 debounced 函数中获取返回值。

$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记录一个通话。

于 2012-11-10T06:50:28.387 回答
54

Angular 1.3 已将 debounce 作为标准

值得一提的是,Angular 1.3 内置了 debounce。如您所料,它是作为指令实现的。你可以这样做:

<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

于 2014-09-30T16:27:28.140 回答
33

自从我写了上面的评论后,我对此有了一些改变。

简短的回答是,您不需要去抖动返回值的函数。

为什么?好吧,从哲学上讲,我认为对事件进行去抖动并且仅对事件进行去抖动更有意义。如果你有一个方法返回一个你想要去抖动的值,你应该去去抖动导致你的方法在下游运行的事件。

于 2013-08-09T21:14:00.030 回答
8

Pete BD 为 debounce 服务提供了一个良好的开端,但是,我发现了两个问题:

  1. 如果您需要更改调用者的状态,则在您应该发送使用 javascript 闭包的 work() 回调时返回。
  2. 超时变量 - 超时变量不是问题吗?超时[] 也许?想象一下使用 debounce 的 2 个指令 - 信号器,输入表单验证器,我相信工厂方法会崩溃。

我目前使用的是:

我将工厂更改为服务,因此每个指令都会获得一个新的去抖动实例,也就是超时变量的新实例。- 我还没有遇到过 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);
                });
            }
        };
    }])
于 2014-02-26T23:42:10.247 回答
2

去抖动服务和指令都有一个很好的实现,可以与任何 ng-model 一起工作:https ://github.com/shahata/angular-debounce

或者简单地使用以下方式安装它:

bower install ng-debounce
于 2014-03-21T09:13:28.937 回答
1

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();
            });
        }
    };
});
于 2013-10-25T09:58:17.893 回答
1

如果您正在处理模型交互,则对此的支持已在 angularjs#1.3.0.beta6 中得到支持。

https://docs.angularjs.org/api/ng/directive/ngModelOptions

于 2014-04-28T13:42:08.370 回答