为 bluebird.js 承诺解决设置最小延迟

Set minimum delay on bluebird.js promise resolution

我想保证 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 函数加入以 Promise.delay 最少 5 秒延迟包装请求的承诺,以实现相同的目的:

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 周围挖掘,但找不到直接执行此操作的函数。

我的问题很简单 - 有谁能提出比第二个示例更清晰、更明确的方式来使用 bluebird 实现此行为?

api 提供此功能的其他 promise 库的任何建议也将不胜感激。

我相信你只需要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);
}

你的问题

首先,其他3秒调用的promisification在这里无关紧要,不应该是promise的一部分。虽然我很荣幸您喜欢我用 .join 的回答,但它也不是我在这里实际使用的工具。

首先,API 调用只是一个任意承诺 returning 函数。

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

这需要一个任意的承诺,return 是一个至少需要 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
}); 

一个更普遍的问题

通常当人们问起这个问题时,他们真正关心的是让一个函数return最多每隔几毫秒产生一个结果,或者让一个函数充当监视器 调用的频率。这是为了限制对限速 Web 服务的调用次数。这应该限制在 return 承诺 的函数的 级别。例如:

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

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

但这只是冰山一角,因为该库允许您实施更灵活、更高级(如果需要)的策略来处理 promise 请求。

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

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

这是一个最小延迟 Promise 的代码示例:

const sleep = async ms => new Promise(resolve => setTimeout(resolve, ms));

const pMinDelay = async (promise, ms) => {
  const [result] = await Promise.all([promise, sleep(ms)]);

  return result;
};

export default pMinDelay;

在您想要延迟 Promise 的地方导入 pMinDelay 函数。

希望对您有所帮助 ;)