【问题标题】:How can I throttle stack of api requests?如何限制 api 请求堆栈?
【发布时间】:2016-05-27 03:08:51
【问题描述】:

我有一个 id 数组,我想为每个 id 发出一个 api 请求,但我想控制每秒发出多少请求,或者更好的是,任何时候只有 5 个打开的连接,以及何时一个连接完成,获取下一个。

目前我有这个,它只是同时触发所有请求:

_.each([1,2,3,4,5,6,7,8,9,10], function(issueId) {
    github.fetchIssue(repo.namespace, repo.id, issueId, filters)
        .then(function(response) {
            console.log('Writing: ' + issueId);
            writeIssueToDisk(fetchIssueCallback(response));
        });
});

【问题讨论】:

  • 你想要有上限的并发承诺。查看ES6 Promise Pool,以及该页面上指定的替代列表。或者,您可以实现自己的计数信号量:D
  • 我遇到了类似的问题,我需要对通过的聊天消息进行速率限制。有一种算法称为“漏桶”。 en.wikipedia.org/wiki/Leaky_bucket我认为这与您面临的情况相似。
  • 我很惊讶没有人提到async。您的“理想”请求听起来像是 eachLimit 的工作
  • @PatrickRoberts:我很惊讶没有人阅读我的链接 - ES6 Promise Pool 的 Alternatives 清楚地列出了 async 的 queue()
  • @Amadan 那么你必须为我重新定义“清楚地”。这相当于期望我阅读细则。

标签: javascript node.js promise underscore.js throttling


【解决方案1】:

就个人而言,我会使用 Bluebird 的 .map()concurrency 选项,因为我已经在使用 Promise 和 Bluebird 进行任何异步操作。但是,如果您想看看限制一次可以运行多少并发请求的手动编码计数器方案是什么样的,这里有一个:

function limitEach(collection, max, fn, done) {
    var cntr = 0, index = 0, errFlag = false;

    function runMore() {
        while (!errFlag && cntr < max && index < collection.length) {
            ++cntr;
            fn(collection[index++], function(err, data) {
                --cntr;
                if (errFlag) return;
                if (err) {
                    errFlag = true;
                    done(err);
                } else {
                   runMore();
                }
            });
        }
        if (!errFlag && cntr === 0 && index === collection.length) {
            done();
        }
    }
    runMore();
}

【讨论】:

  • 这是很棒的东西。我正在使用 Q,现在我正在尝试弄清楚如何使用 Promise 简化此实用程序 - Q 中没有与并发映射等效的方法吗?
  • 不在 Q 本身中,但请参阅 qlimit 将其添加到 Q 中(并且本身就像 20 行代码)。另外,您可能会考虑迁移到 Bluebird - API 非常相似,还有其他 reasons
  • 我知道这是一个老问题。我使用了这个非常好的函数并将其修改为使用 async/await 并为多个函数使用相同的限制(因此可以在端点级别或 api 级别或两者上应用该限制)。将其添加为单独的答案,因为它不会包含在评论中。
【解决方案2】:

与蓝鸟:

function fetch(id) {
  console.log("Fetching " + id);
  return Promise.delay(2000, id)
  .then(function(id) {
    console.log(" Fetched " + id);
  });
}

var ids = [1,2,3,4,5,6,7,8,9,10];
Promise.map(ids, fetch, { concurrency: 3 });
<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.1/bluebird.min.js"></script>

<!-- results pane console output; see http://meta.stackexchange.com/a/242491 -->
<script src="http://gh-canon.github.io/stack-snippet-console/console.min.js"></script>

【讨论】:

    【解决方案3】:

    将您的数据分成任意数量的数组,以实现并发连接。使用 setTimeout 进行调度,并让完成回调处理子数组的其余部分。

    将 setTimeout 包装在它自己的函数中,以便在调用延迟_fetch() 时将变量值冻结为其值。

    function delayed_fetch(delay, namespace, id, issueIds, filters) {
      setTimeout(
        function() { 
          var issueId=issueIds.shift();
          github.fetchIssue(namespace, id, issueId, filters).then(function(response) {
            console.log('Writing: ' + issueId);
            writeIssueToDisk(fetchIssueCallback(response));
            delayed_fetch(0, namespace, id, issueIds, filters);
          });
        }, delay);
    }
    
    var i=0;
    _.each([ [1,2] , [3,4], [5,6], [7,8], [9,10] ], function(issueIds) {
            var delay=++i*200; // millisecond
            delayed_fetch(delay, repo.namespace, repo.id, issueIds, filters);
    });
    

    【讨论】:

    • 浪费时间。如果一个存储桶有五个项目,其中四个在 100 毫秒内完成,一个在 10 秒内完成,那么您的代码将花费 100 毫秒(并发 5)和 9.1 秒(并发 1),然后再继续。
    • 当然可以,但是这个具体的例子涉及到同等重量的任务,并且在实践中,它工作得很好。它在概念上比替代方案更简单,后者涉及变量范围地狱和主管循环。 jfriend00 的解决方案已经比我的长了,需要再长两倍左右才能真正解决问题。
    • 鉴于 OP 已确认他已经使用了至少三个库(lodash 或下划线、Q 以及 github.fetchIssue 来自的任何内容),jfriend00 的解决方案和我的解决方案都只有一行。此外,我在他的帖子中没有看到任何可以描述为“变量范围地狱”的内容,并且 OP 的代码中已经存在循环。
    【解决方案4】:

    我建议为此使用喉咙:https://github.com/ForbesLindesay/throat

    【讨论】:

      【解决方案5】:

      使用蓝鸟

      function getUserFunc(user) {
        //Get a collection of user
      }
      
      function getImageFunc(id) {
        //get a collection of image profile based on id of the user
      }
      
      function search(response) {
        return getUsersFunc(response).then(response => {
          const promises = response.map(items => return items.id);
          const images = id => {
              return getImagesFunc(id).then(items => items.image);
          };
          return Promise.map(promises, images, { concurrency: 5 });
        });
      }
      

      之前我使用 ES6 函数 Promise.all(),但它不像我期望的那样工作。然后使用第三方库 bluebird.js 并像魅力一样工作。

      【讨论】:

        猜你喜欢
        • 2016-01-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-03
        • 2014-03-12
        • 2012-11-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多