设置bluebird.js承诺解析的最小延迟

时间:2015-01-29 18:57:33

标签: javascript promise bluebird

我想保证解决bluebird.js承诺的最小延迟。

作为一个例子,假设我正在将一个请求包含在一个承诺中。我想要的行为是,如果请求少于5秒,我想人为地将承诺解析的延迟增加到5秒。如果请求超过5秒,我不希望添加任何人工延迟 - 所以它比为每个请求添加静态延迟要复杂一些。所有这一切都应该完全隐藏在承诺的消费者身上 - 他们应该看到承诺在5秒或更长时间内得到解决。

为了演示,我有一个简单的模拟实现示例,它将模拟的请求延迟硬编码为3秒。

我的第一次尝试是这样的 - 使用setTimeout确保在5秒之前没有调用解析回调。

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

很多样板。

然后我通过this answer发现我可以使用Promise.join函数来加入包含请求的承诺,并使用最少5秒延迟的Promise.delay来实现同样的目的:

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

这个更干净,但仍然比我想要的更多样板 - 我在bluebird api reference周围挖了一个并且找不到直接执行此操作的函数。

我的问题很简单 - 任何人都可以建议使用蓝鸟比第二个例子更清晰,更具说服力的方式来实现这种行为吗?

api提供此功能的其他承诺库的任何建议也将受到赞赏。

3 个答案:

答案 0 :(得分:6)

我相信您需要做的就是Promise.delay(value).return(promise)

您可以将其包装在效用函数中:

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/

注意关于这一点的一点是,如果承诺拒绝,延迟的承诺将在五秒钟过去之前拒绝。这可能是理想的行为(正如@Benjamin Gruenbaum在评论中指出的那样),但如果您希望它立即拒绝,还有两个选择:

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

或@Benjamin Gruenbaum与Promise.all的方法:

function minDelay(promise, delay) {
    Promise.all([promise, Promise.delay(delay)]).get(0);
}

答案 1 :(得分:4)

您的问题

首先,其他3秒电话的宣传在这里是无关紧要的,它不应该是承诺的一部分。虽然我很受宠若惊,但我喜欢.join的回答,但这也不是我实际使用的工具。

首先,API调用只是一个任意的promise返回函数。

function yourApiCall(){
    // your function that returns a promise AFTER promisificatin
}

实际上,我们并不关心它。它可能只是:

var p = ... ; //p is a promise

现在我们要确保在解决p。

之前至少经过3秒
function minDelay(p, ms){ // stealing name from JLRishe's answer
    return Promise.all([p, Promise.delay(ms)]).get(0);
}

采用任意承诺并返回至少ms毫秒才能解析的承诺。

minDelay(p, 300).then(function(el){
   // el is minDelay's return value and at least 300 ms have passed
});

你也可以把它放在Bluebird的原型上(如果你正在写一个库,一定要先得到你自己的孤立副本):

Promise.prototype.minDelay = function minDelay(ms){
    // note that unlike the example above this will delay 
    // on rejections too 
    return Promise.delay(ms).return(this);
}

这会让你以声明的方式做到:

p.minDelay(300).then(function(res){
   // access result
}); 

更普遍的问题

通常,当人们询问这个问题时,他们真正关心的是使函数最多每毫秒返回一个结果,或者使函数充当监视器以确定调用的频率。这是为了限制对速率限制的Web服务进行的调用次数。这应该限制在返回promise 的函数的级别。例如:

var queue = Promise.resolve();
function throttle(fn, ms){
    var res = queue.then(function(){ // wait for queue
        return fn(); // call the function
    });
    queue = Promise.delay(ms).return(queue); // make the queue wait
    return res; // return the result
}

这可以让你这样做:

function myApiCall(){
    // returns a promise
}
var api = throttle(myApiCall, 300); // make call at most every 300 ms;

api(); // calls will be sequenced and queued
api(); // calls will be made at most every 300 ms
api(); // just be sure to call this directly, return this to consumers

答案 2 :(得分:-1)

spex专门用于处理使用promises时数据限制和负载平衡等问题。

在您的情况下,我们可以使用以下示例:

var spex = require('spex')(Promise);

function source(index, data, delay) {
    var start = Date.now();
    return new Promise(function (resolve) {
        // request your data here;

        var end = Date.now();
        if (end - start < 5000) {
            setTimeout(function () {
                resolve();
            }, 5000 - end + start);
        } else {
            resolve();
        }
    });
}

function dest(index, data, delay) {
    // you can do additional load balancing here,
    // while processing the data;
}

spex.sequence(source, dest)
    .then(function (data) {
        console.log("DATA:", data);
    });

但这只是表面上的问题,因为该库可以让您实现更灵活,更先进(如果您需要)的策略来处理承诺请求。

对于您的情况,可能有趣的是参数delay,它们被传递到源函数和目标函数中,因此负载平衡可以在需要时以双向方式处理。

此外,您可以使用具有相同负载平衡策略的方法page,但在页面中处理请求。