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

use*_*092 5 javascript arrays settimeout node.js promise

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

但请注意,虽然.map是同步的,但它会立即返回a 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));
Run Code Online (Sandbox Code Playgroud)

jfr*_*d00 9

有很多不同的方法来解决这个问题.我可能只是自己使用递归链式承诺,然后你可以更准确地使用基于前一次调用的结束的计时器,你可以使用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
});
Run Code Online (Sandbox Code Playgroud)

使用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);
    })
}
Run Code Online (Sandbox Code Playgroud)

使用await来暂停循环

并且,您可以await在ES6中使用"暂停"循环:

// 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));
}
Run Code Online (Sandbox Code Playgroud)

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

如果您没有尝试累积一系列结果,但只是想要排序,那么规范的方法是使用由.reduce()以下驱动的promise链:

// 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
});
Run Code Online (Sandbox Code Playgroud)

使用Bluebird功能进行并发控制和延迟

蓝鸟承诺库内置了这里帮助一对夫妇有用的功能:

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
});
Run Code Online (Sandbox Code Playgroud)

请注意,使用该{concurrency: 1}功能可以控制同时传输的请求数和内置的.delay(100)promise方法.


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

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

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);
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

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

// 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
});
Run Code Online (Sandbox Code Playgroud)


Ric*_*her 6

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

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'))
Run Code Online (Sandbox Code Playgroud)

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

理想情况下,您会将其委托给后台的工作进程,以免阻塞。但是,假设您现在不打算这样做,一个技巧是将每个呼叫延迟不同的时间。(请注意,这并没有解决多个用户尝试处理一次大名单,这大概是要触发相同的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'))
Run Code Online (Sandbox Code Playgroud)

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