如何在每个调用栈中仅调用一次函数?

3

假设我有一个服务器,每当客户端的数据发生更改时,它都会向其客户端发送数据。客户端类如下:

function Client() {
    this.data = {};
    this.update = function (key, value) {
        this.data[key] = value;
        this.emitUpdate();
    };

    this.emitUpdate = function () {
        // tell server to send this client's data
    };
}

var myClient = new Client();

如果我只是改变一个东西:

myClient.update("name", "John");

服务器会使用客户端的新信息更新所有客户端,这很好。
但是,如果我同时从我的应用程序中的不同位置更改多个内容:
if (something === true) {
    myClient.update("something", true);
} else {
    myClient.update("something_else", true);
}

myClient.update("age", Date.now());

总会有两件事情被改变,emitUpdate() 将被调用两次。服务器将发送数据两次,所有客户端都必须渲染两次。想象一下发生100个这样的更改... 这将是一个很大的开销,考虑到服务器可以只发送一次更新,因为所有这些更改都发生在同一时间段内。

我如何使 emitUpdate() 在每个调用栈中仅被调用一次?我使用了 underscore.js 并查看了 defer()throttle() 函数。问题是,我需要它们的效果结合起来。

var log = function () {
  console.log("now");
};

// logs "now" 10 times
for (let i = 0; i < 10; i++) {
  _.defer(log);
}

// logs "now" 10 times
var throttled = _.throttle(log, 0);
for (let i = 0; i < 10; i++) {
  throttled();
}

如果我使用 throttle() 并且使用不同的 wait(如 12),结果会不一致 - 有时只调用一次,有时调用多次。
我需要这样一个函数:
// logs "now" once
var magic = _.deferottle(log);
for (let i = 0; i < 10; i++) {
  magic();
}

有没有一种方法能够实现它?

为什么不将 emitUpdateupdate 分开,并根据需要调用前者呢? - hindmost
为什么不在更新结束时防抖以发出更改信号?https://dev59.com/YV8e5IYBdhLWcg3wLYHt http://underscorejs.org/#debounce - corn3lius
3个回答

3
这应该适用于多个同步调用update()方法的情况:

function Client() {
  this.data = {};
  this.update = function (key, value) {
    this.data[key] = value;
    if (!this.aboutToUpdate) {
      this.aboutToUpdate = setTimeout(() => {
        this.aboutToUpdate = false
        this.emitUpdate()
      })
    }
  };

  this.emitUpdate = function () {
    // tell server to send this client's data
    console.log('update sent', this.data)
  };
}

// This should only log 'update sent' once
var client = new Client()
for (var i = 0; i < 5; i++) {
  client.update(i, i)
}


2

实际上,你不需要使用throttle,只需与一些布尔标志检查一起使用defer即可。

类似于这样:

this.update = function (key, value) {
    this.data[key] = value;
    if (this.isQueued) return;
    this.isQueued = true;
    _.defer(function () {
        this.isQueued = false;
        this.emitUpdate();
    }.bind(this));
};

你意识到这跟我的答案一模一样,除了依赖于underscore并且比我晚14分钟。 - idbehold

0
洗澡时我突然想到了一个解决方案:

var deferottle = function(func) {
  var called, args, ctx;

  return function() {
    args = arguments;
    ctx = this;

    if (!called) {
      called = true;
      setTimeout(function() {
        func.apply(ctx, args);
        called = false;
      });
    }
  };
};

var magic = deferottle(function(num) {
  console.log("num: ", num);
});

for (let i = 0; i < 10; i++) {
  magic(i);
}

原来 debounce() 等待时间为 0 也可以使用:

var magic = _.debounce(function(num) {
  console.log("num: ", num);
}, 0);

for (let i = 0; i < 10; i++) {
  magic(i);
}
<script src="http://underscorejs.org/underscore-min.js"></script>


1
这并不是你问题的真正解决方案,因为它只会传播最后一次调用。例如 myClient.update("something_else", true); myClient.update("age", Date.now()); 只会更新 age 键。 - idbehold
是的,它不能解决我的答案的第一部分,但它是第二部分的解决方案。实际上,underscore 的 debounce() 实现类似于我的 deferottle() 函数。不过还是感谢你指出来! - dodov
我是指我的问题的第一部分。而且我有一个额外的逗号。 - dodov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接