Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set minimum delay on bluebird.js promise resolution

I want to guarantee a minimum delay on the resolution of a bluebird.js promise.

As an example, let's say I'm making a request wrapped in a promise. The behaviour I want is that if the request takes less than 5 seconds, I want to artificially increase the delay of the promise resolution to 5 seconds. If the request were to take more than 5 seconds, I'd want no artificial delay added - so it's a little more complicated than just adding a static delay to every request. All of this should be completely hidden from the consumer of the promise - they should just see the promise being resolved in 5 seconds, or more.

To demonstrate, I've got a simple mock implementation example which hardcodes the mocked request latency at 3 seconds.

My first try went something like this - using a setTimeout to ensure the resolve callback isn't called before 5 seconds have passed.

fiddle here

function getTimestamp() {
  return new Date().getTime();   
}

function makeCallWith3SecondLatency(cb) {
  console.log('mocking a call with 3 second latency...');
  var mockResult = 'the result';
  setTimeout(function() { cb(mockResult); }, 3000);
}

function doSomethingAsync(minDelay) {
  return new Promise(function(resolve) {
    var calledAt = getTimestamp();
    makeCallWith3SecondLatency(function(arg) {
      var actualDelay = getTimestamp() - calledAt;
      if(actualDelay < minDelay) {
        var artificialDelay = minDelay - actualDelay;
        console.log('artificially delay another ' + artificialDelay + ' millis');
        setTimeout(function() { resolve(arg); }, artificialDelay);
      } else {
        resolve(arg);
      }
    });
  });
}

function printResult(result) {
  console.log('result: ' + result)   
}

var minDelay = 5000;
doSomethingAsync(minDelay).then(printResult);

A lot of boilerplate.

I then discovered through this answer that I could use the Promise.join function to join the promise wrapping the request with a Promise.delay of the minimum 5 second delay to achieve the same thing:

fiddle here

function makeCallWith3SecondLatency(cb) {
  console.log('mocking a call with 3 second latency...');
  var mockResult = 'the result';
  setTimeout(function() { cb(mockResult); }, 3000);
}

function doSomethingAsync(minDelay) {
  return Promise.join(
                new Promise(function(resolve) { makeCallWith3SecondLatency(resolve); }),
                Promise.delay(minDelay).then(function() { console.log('artificially delaying 5 seconds with Promise.delay') }),
                function(result) { return result; });
}

function printResult(result) {
  console.log('result: ' + result)   
}

var minDelay = 5000;
doSomethingAsync(minDelay).then(printResult);

This is cleaner, but still a little more boilerplate than I'd like - I've dug around the bluebird api reference and can't find a function which does this directly.

My question is simple - can anybody suggest a cleaner, more declarative way of achieving this behaviour with bluebird than the second example?

Any suggestions of other promise libraries where the api does offer this would also be appreciated.

like image 460
davnicwil Avatar asked Dec 24 '22 23:12

davnicwil


1 Answers

I believe that all you need to do is Promise.delay(value).return(promise):

You can wrap it in a utility function:

function stallPromise(promise, delay) {
    return Promise.delay(delay).return(promise);
}

function doSomethingAsync(minDelay) {
    var p = new Promise(makeCallWith3SecondLatency); 

    return stallPromise(p, minDelay);
}

var minDelay = 5000;
doSomethingAsync(minDelay).then(printResult);

http://jsfiddle.net/s572rg7y/1/

Note that one thing about this is that if the promise rejects, the delayed promise will not reject until the five seconds have elapsed. This may be the desired behavior (as @Benjamin Gruenbaum points out in the comments), but if you would prefer for it to reject immediately, two other options are:

With Promise.join:

function stallPromise(promise, delay) {
    // if you're using underscore/lodash, you can use _.identity for this
    function identity(val) { return val; }

    return Promise.join(promise, Promise.delay(delay), identity);
}

Or @Benjamin Gruenbaum's approach with Promise.all:

function minDelay(promise, delay) {
    Promise.all([promise, Promise.delay(delay)]).get(0);
}
like image 189
JLRishe Avatar answered Dec 27 '22 14:12

JLRishe