向返回承诺数组的 Array.map 调用添加毫秒延迟

Posted

技术标签:

【中文标题】向返回承诺数组的 Array.map 调用添加毫秒延迟【英文标题】:Add milliseconds delay to Array.map calls which returns Array of promises 【发布时间】:2018-05-17 11:10:42 【问题描述】:

我的需求很简单。我想将对sendEmail 的呼叫延迟100 毫秒。电子邮件服务提供商允许每秒最多发送 10 封电子邮件。

但是请注意,尽管.map 是同步的,但它会立即返回Promise

我试过setTimeout没有用,比如setTimeout(() => resolve(x), 100)setTimeout(() => return new Promise...., 100)

想法?

const promises = userEmailArray.map((userEmail) => 
  return new Promise((resolve, reject) => 
      ....
      mailer.sendEmail(userEmail);
      return resolve();
    );
  );
);
...
Promise.all(promises).then(() => resolve()).catch(error => reject(error));

【问题讨论】:

你的邮件是什么?它给你什么,让你知道它什么时候被发送?您希望能够等待它通过承诺或回调来响应您。 这是在 express 或 web 应用程序中运行的吗?如果是这样,那么可能会向服务器发出多个请求,从而导致您的 api 失败,因为即使您将请求限制为每秒不超过 10 个,您的函数也可能会被多次调用。 【参考方案1】:

有很多不同的方法可以解决这个问题。我自己可能只使用递归链式 Promise,然后您可以更精确地使用基于上一次调用的完成的计时器,您可以使用 Promise 来调用它并处理错误的传播。

我在这里假设您的 mailer.sendEmail() 遵循 node.js 回调调用约定,因此我们需要“承诺”它。如果它已经返回了一个 promise,那么你可以直接使用它来代替我创建的 sendEmail() 函数。

无论如何,这里有很多不同的方法。

延迟后调用相同的函数(延迟递归)

// make promisified version - assumes it follows node.js async calling convention
let sendEmail = util.promisify(mailer.sendEmail);

function delay(t, data) 
    return new Promise(resolve => 
        setTimeout(resolve.bind(null, data), t);
    );


function sendAll(array) 
    let index = 0;
    function next() 
        if (index < array.length) 
            return sendEmail(array[index++]).then(function() 
                return delay(100).then(next);
            );
                
    
    return Promise.resolve().then(next);


// usage
sendAll(userEmailArray).then(() => 
    // all done here
).catch(err => 
    // process error here
);

使用 setInterval 控制步速

您也可以使用 setInterval 来每隔 100 毫秒发起一个新请求,直到数组为空:

// promisify
let sendEmail = util.promisify(mailer.sendEmail);

function sendAll(array) 
    return new Promise((resolve, reject) => 
        let index = 0;
        let timer = setInterval(function() 
            if (index < array.length) 
                sendEmail(array[index++]).catch(() => 
                    clearInterval(timer);
                    reject();                        
                );
             else 
                clearInterval(timer);
                resolve();
            
        , 100);
    )


使用 await 暂停循环

而且,您可以在 ES6 中使用 await 来“暂停”循环:

// make promisified version - assumes it follows node.js async calling convention
let sendEmail = util.promisify(mailer.sendEmail);

function delay(t, data) 
    return new Promise(resolve => 
        setTimeout(resolve.bind(null, data), t);
    );


// assume this is inside an async function    
for (let userEmail of userEmailArray) 
    await sendEmail(userEmail).then(delay.bind(null, 100));


.reduce() 与 Promises 一起使用以对数组的访问进行排序

如果您不尝试累积结果数组,而只是想排序,那么一种规范的方法是使用由.reduce() 驱动的承诺链:

// make promisified version - assumes it follows node.js async calling convention
let sendEmail = util.promisify(mailer.sendEmail);

function delay(t, data) 
    return new Promise(resolve => 
        setTimeout(resolve.bind(null, data), t);
    );


userEmailArray.reduce(function(p, userEmail) 
    return p.then(() => 
        return sendEmail(userEmail).then(delay.bind(null, 100));
    );
, Promise.resolve()).then(() => 
    // all done here
).catch(err => 
    // process error here
);

将 Bluebird 功能用于并发控制和延迟

Bluebird promise library 内置了一些有用的功能,可在此处提供帮助:

const Promise = require('Bluebird');
// make promisified version - assumes it follows node.js async calling convention
let sendEmail = Promise.promisify(mailer.sendEmail);

Promise.map(userEmailArray, userEmail => 
    return sendEmail(userEmail).delay(100);
, concurrency: 1).then(() => 
    // all done here
).catch(err => 
    // process error here
);

注意使用concurrency: 1 功能来控制同时进行多少请求和内置.delay(100) promise 方法。


创建通用的序列助手

而且,创建一个小辅助函数来对具有迭代之间延迟的数组进行排序可能会很有用:

function delay(t, data) 
    return new Promise(resolve => 
        setTimeout(resolve, t, data);
    );


async function runSequence(array, delayT, fn) 
    let results = [];
    for (let item of array) 
        let data = await fn(item);
        results.push(data);
        await delay(delayT);
    
    return results;

然后,您可以在需要时使用该助手:

// make promisified version - assumes it follows node.js async calling convention
let sendEmail = util.promisify(mailer.sendEmail);

runSequence(userEmailArray, 100, sendEmail).then(() => 
    // all done here
).catch(err => 
    // process error here
);

【讨论】:

@user1322092 - 我总共添加了 6 种不同的方法来做到这一点。 使用 await 暂停循环 将不起作用,因为回调将立即为数组中的每个项目返回一个承诺。所以在 100ms 之后,所有的调用基本上都会一次完成。 @Inkling - 明白了。现已更正。有一段时间不知道你在看什么。 @lowtechsun - 我修复了参数顺序问题并修复了导致答案中这两个警告的原因。您可以将这些更改应用到您的小提琴。 @lowtechsun - 对不起,我不会再处理你的 jsFiddle 了。最后一次更新here。如果您对特定的 jsFiddle 有疑问,那么您可以发布您自己的问题。仅供参考,您无需在 fetchhtml 中收集结果,因为 runSequence() 已经在收集结果。【参考方案2】:

只对数组进行异步迭代更简单。

function send(i, arr, cb) 
  if (i >= arr.length) return cb();

  mailer.sendEmail(arr[i]);
  setTimeout(send, 100, i+1, arr, cb);

send(0, userEmailArray, function()  console.log("all done") );

【讨论】:

谢谢 - 但你如何结合承诺?在发回响应之前,我需要解决所有的承诺 (Promise.all)(或者如果一个被拒绝,我猜会返回一个错误)。 @user1322092:你不需要承诺。让send 接收一个回调,该回调被调用if 语句。这样代码更短、更简单、更清晰。【参考方案3】:

您已经有一个“队列”:要发送到的地址列表。你现在真正需要做的就是在发送每一个之前暂停。但是,您不想在每次发送之前暂停相同的时间长度。这将导致 n 毫秒的单次暂停,然后在几毫秒内发送大量消息。试试运行这个,你就会明白我的意思了:

const userEmailArray = [ 'one', 'two', 'three' ]
const promises = userEmailArray.map(userEmail =>
  new Promise(resolve =>
    setTimeout(() => 
      console.log(userEmail)
      resolve()
    , 1000)
  )
)
Promise.all(promises).then(() => console.log('done'))

希望您看到大约一秒钟的停顿,然后立即出现一堆消息!不是我们真正想要的。

理想情况下,您应该将此委托给后台的工作进程,以免阻塞。但是,假设您现在不打算这样做,一个技巧是让每个呼叫延迟不同的时间。 (请注意,这不能解决多个用户尝试同时处理大型列表的问题,这可能会触发相同的 API 限制)。

const userEmailArray = [ 'one', 'two', 'three' ]
const promises = userEmailArray.map((userEmail, i) =>
  new Promise(resolve =>
    setTimeout(() => 
      console.log(userEmail)
      resolve()
    , 1000 * userEmailArray.length - 1000 * i)
  )
)
Promise.all(promises).then(() => console.log('done'))

在这里,您应该看到每个数组元素都以大致交错的方式处理。同样,这不是一个可扩展的解决方案,但希望它展示了一些关于时间和承诺的信息。

【讨论】:

谢谢,Rich,非常感谢您的见解! YW。不过,我不能强调不要大规模这样做;)它可能对仍在学习承诺如何运作的人有所帮助。

以上是关于向返回承诺数组的 Array.map 调用添加毫秒延迟的主要内容,如果未能解决你的问题,请参考以下文章

尝试使用函数调用返回时,array.map 给出未定义 [重复]

错误:超过 2000 毫秒的超时。带有承诺的单元测试

遍历map的方法

数组的方法 Array.map();Array.every()和Array.some();数组的indexof();

JavaScript Array map() 方法

Array的 map() 和 reduce()