向Array.map调用添加毫秒延迟,返回Promises数组

时间:2017-12-04 02:29:06

标签: javascript arrays node.js promise settimeout

我的需要很简单。我想将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));

3 个答案:

答案 0 :(得分:7)

有很多不同的方法可以解决这个问题。我可能只是自己使用递归链式承诺,然后你可以更准确地使用基于前一次调用的结束的计时器,你可以使用promises来调用它并处理错误的传播。

我假设你的mailer.sendEmail()遵循node.js回调调用约定,所以我们需要“promisify”它。如果它已经返回一个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);
    })
}

使用等待暂停循环

而且,您可以在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)承诺方法。

创建一般可以使用的序列助手

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

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

async function runSequence(array, delayT, fn) {
    for (item of array) {
        await fn(item).then(data => {
            return delay(delayT, data);
        });
    }
}

然后,您可以在需要时使用该帮助程序:

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

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

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

你已经有一个队列&#39;排序:要发送到的地址列表。现在你真正需要做的就是在发送每个之前暂停。但是,您不希望在每次发送之前暂停相同的时间。这将导致n ms的单个暂停,然后在几毫秒内发送一大堆消息。尝试运行这个,你就会明白我的意思:

&#13;
&#13;
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'))
&#13;
&#13;
&#13;

希望你看到一秒钟的暂停,然后一堆消息立刻出现!不是我们真正想要的。

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

&#13;
&#13;
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'))
&#13;
&#13;
&#13;

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