How to invoke a function only once per call stack?

Let's say I have a server that sends data to its clients each time a client's data has changed. The client class looks like this:

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();

If I change just one thing:

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

the server updates all clients with this client's new information. Great.

But...

If I change multiple things at once from different places in my app:

if (something === true) {
    myClient.update("something", true);
} else {
    myClient.update("something_else", true);
}

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

there will always be two things changed and emitUpdate() will be called twice. The server will send the data twice and all clients will have to render it twice. Imagine 100 changes like that occurring... It would be a big overhead, considering the server could send the update just once, since all of these changes happen in one time frame.

How do I make emitUpdate() invoke only once per call stack? I use underscore.js and checked out the defer() and throttle() functions. Problem is, I need their effects combined.

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();
}

If I use throttle() with a different amount of wait like 1 or 2, the results are inconsistent - sometimes it's called once, sometimes more.

I need something like this:

// logs "now" once
var magic = _.deferottle(log);
for (let i = 0; i < 10; i++) {
  magic();
}

Is there a way I could achieve it?

Answers:

Answer

This should work for multiple synchronous calls to the update() method:

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)
}

Answer

Actually you don't need throttle, just use defer in conjunction with some boolean flag check.

Something like this:

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));
};
Answer

A solution popped in my head while taking a shower:

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);
}

Turns out debounce() with a wait of 0 can also be used:

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>

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.