我正在尝试为下面的promiseRateLimit
函数创建一个有效的测试用例。 promiseRateLimit
函数的工作方式是使用queue
来存储传入的promises,并在它们之间放置delay
。
import Promise from 'bluebird'
export default function promiseRateLimit (fn, delay, count) {
let working = 0
let queue = []
function work () {
if ((queue.length === 0) || (working === count)) return
working++
Promise.delay(delay).tap(() => working--).then(work)
let {self, args, resolve} = queue.shift()
resolve(fn.apply(self, args))
}
return function debounced (...args) {
return new Promise(resolve => {
queue.push({self: this, args, resolve})
if (working < count) work()
})
}
}
以下是该功能的实例。
async function main () {
const example = (v) => Promise.delay(50)
const exampleLimited = promiseRateLimit(example, 100, 1)
const alpha = await exampleLimited('alpha')
const beta = await exampleLimited('beta')
const gamma = await exampleLimited('gamma')
const epsilon = await exampleLimited('epsilon')
const phi = await exampleLimited('phi')
}
example
承诺需要50ms
才能运行,promiseRateLimit
功能只允许1
承诺100ms
。所以promises之间的间隔应该大于100ms
。
这是一个完整的测试,有时会返回成功,有时会失败:
import test from 'ava'
import Debug from 'debug'
import Promise from 'bluebird'
import promiseRateLimit from './index'
import {getIntervalsBetweenDates} from '../utilitiesForDates'
import {arraySum} from '../utilitiesForArrays'
import {filter} from 'lodash'
test('using async await', async (t) => {
let timeLog = []
let runCount = 0
const example = (v) => Promise.delay(50)
.then(() => timeLog.push(new Date))
.then(() => runCount++)
.then(() => v)
const exampleLimited = promiseRateLimit(example, 100, 1, 'a')
const alpha = await exampleLimited('alpha')
const beta = await exampleLimited('beta')
const gamma = await exampleLimited('gamma')
const epsilon = await exampleLimited('epsilon')
const phi = await exampleLimited('phi')
const intervals = getIntervalsBetweenDates(timeLog)
const invalidIntervals = filter(intervals, (interval) => interval < 100)
const totalTime = arraySum(intervals)
t.is(intervals.length, 4)
t.deepEqual(invalidIntervals, [])
t.deepEqual(totalTime >= 400, true)
t.is(alpha, 'alpha')
t.is(beta, 'beta')
t.is(gamma, 'gamma')
t.is(epsilon, 'epsilon')
t.is(phi, 'phi')
})
我创建了一个getIntervalsBetweenDates
函数,它只是简化了两个unix时间戳,并获得了一组日期之间的持续时间。
export function getIntervalsBetweenDates (dates) {
let intervals = []
dates.forEach((date, index) => {
let nextDate = dates[index + 1]
if (nextDate) intervals.push(nextDate - date)
})
return intervals
}
问题是上面的测试有时会返回一个低于delay
的间隔。例如,如果delay
为100ms
,则有时间隔会返回98ms
或96ms
。没有理由这样做。
有没有办法让上述测试100%的时间通过?我正在努力确保delay
参数有效,并且承诺之间至少有那么多时间。
更新2016-12-28 9:20 am(EST)
这是完整的测试
import test from 'ava'
import Debug from 'debug'
import Promise from 'bluebird'
import promiseRateLimit from './index'
import {getIntervalsBetweenDates} from '../utilitiesForDates'
import {arraySum} from '../utilitiesForArrays'
import {filter} from 'lodash'
test('using async await', async (t) => {
let timeLog = []
let runCount = 0
let bufferInterval = 100
let promisesLength = 4
const example = v => {
timeLog.push(new Date)
runCount++
return Promise.delay(50, v)
}
const exampleLimited = promiseRateLimit(example, bufferInterval, 1)
const alpha = await exampleLimited('alpha')
const beta = await exampleLimited('beta')
const gamma = await exampleLimited('gamma')
const epsilon = await exampleLimited('epsilon')
const phi = await exampleLimited('phi')
const intervals = getIntervalsBetweenDates(timeLog)
const invalidIntervals = filter(intervals, (interval) => interval < bufferInterval)
const totalTime = arraySum(intervals)
t.is(intervals.length, promisesLength)
t.deepEqual(invalidIntervals, [])
t.deepEqual(totalTime >= bufferInterval * promisesLength, true)
t.is(alpha, 'alpha')
t.is(beta, 'beta')
t.is(gamma, 'gamma')
t.is(epsilon, 'epsilon')
t.is(phi, 'phi')
})
test('using Promise.all with 2 promises', async (t) => {
let timeLog = []
let runCount = 0
let bufferInterval = 100
let promisesLength = 1
const example = v => {
timeLog.push(new Date)
runCount++
return Promise.delay(50, v)
}
const exampleLimited = promiseRateLimit(example, bufferInterval, 1)
const results = await Promise.all([exampleLimited('alpha'), exampleLimited('beta')])
const intervals = getIntervalsBetweenDates(timeLog)
const invalidIntervals = filter(intervals, (interval) => interval < bufferInterval)
const totalTime = arraySum(intervals)
t.is(intervals.length, promisesLength)
t.deepEqual(invalidIntervals, [])
t.deepEqual(totalTime >= bufferInterval * promisesLength, true)
})
test('using Promise.props with 4 promises', async (t) => {
let timeLog = []
let runCount = 0
let bufferInterval = 100
let promisesLength = 3
const example = v => {
timeLog.push(new Date)
runCount++
return Promise.delay(200, v)
}
const exampleLimited = promiseRateLimit(example, bufferInterval, 1)
const results = await Promise.props({
'alpha': exampleLimited('alpha'),
'beta': exampleLimited('beta'),
'gamma': exampleLimited('gamma'),
'delta': exampleLimited('delta')
})
const intervals = getIntervalsBetweenDates(timeLog)
const invalidIntervals = filter(intervals, (interval) => interval < bufferInterval)
const totalTime = arraySum(intervals)
t.is(intervals.length, promisesLength)
t.deepEqual(invalidIntervals, [])
t.deepEqual(totalTime >= bufferInterval * promisesLength, true)
t.is(results.alpha, 'alpha')
t.is(results.beta, 'beta')
t.is(results.gamma, 'gamma')
t.is(results.delta, 'delta')
})
test('using Promise.props with 12 promises', async (t) => {
let timeLog = []
let runCount = 0
let bufferInterval = 100
let promisesLength = 11
const example = v => {
timeLog.push(new Date)
runCount++
return Promise.delay(200, v)
}
const exampleLimited = promiseRateLimit(example, bufferInterval, 1)
const results = await Promise.props({
'a': exampleLimited('a'),
'b': exampleLimited('b'),
'c': exampleLimited('c'),
'd': exampleLimited('d'),
'e': exampleLimited('e'),
'f': exampleLimited('f'),
'g': exampleLimited('g'),
'h': exampleLimited('h'),
'i': exampleLimited('i'),
'j': exampleLimited('j'),
'k': exampleLimited('k'),
'l': exampleLimited('l')
})
const intervals = getIntervalsBetweenDates(timeLog)
console.log(intervals)
const invalidIntervals = filter(intervals, (interval) => interval < bufferInterval)
const totalTime = arraySum(intervals)
t.is(intervals.length, promisesLength)
t.deepEqual(invalidIntervals, [])
t.deepEqual(totalTime >= bufferInterval * promisesLength, true)
})
即使example
更改,我仍然会遇到问题。
[ 99, 98, 105, 106, 119, 106, 105, 105, 101, 106, 100 ]
2 passed
2 failed
using Promise.props with 4 promises
t.deepEqual(invalidIntervals, [])
|
[99]
Generator.next (<anonymous>)
using Promise.props with 12 promises
t.deepEqual(invalidIntervals, [])
|
[99,98]
Generator.next (<anonymous>)
答案 0 :(得分:1)
setTimeout
(在Promise.delay
内部使用)并不能保证准确的计时,它只能确保在给定超时到期之前调用回调 。实际时间取决于机器负载,事件循环的速度以及possibly anything else。
事实上,Node.js docs只表明了
callback
可能不会在delay
毫秒内被调用。 Node.js不保证回调何时触发的确切时间,也不保证它们的排序。回调将尽可能接近指定的时间调用。
在您的测试中会发生什么Promise.delay(50)
有时需要超过50毫秒(不是很多,但仍然如此),并且当下一个Promise.delay(50)
时,与后续日志的差异可能会小于100毫秒更准时。
如果您只是立即记录example
函数的调用时间,而不是在人工延迟约 50ms之后,您应该能够减轻这种影响:
const example = v => {
timeLog.push(new Date);
runCount++;
return Promise.delay(50, v)
};
要处理100ms超时本身的不准确性,最简单的解决方案是给它一些可能的5%余量(在你的情况下为5ms):
const invalidIntervals = filter(intervals, (interval) => interval < 100 * .95)
t.true(totalTime >= 400 * .95)
如果你想确保延迟永远不会太短,你可以编写自己的功能:
Promise.delayAtLeast = function(delay, value) {
const begin = Date.now()
return Promise.delay(delay, value).then(function checkTime(v) {
const duration = Date.now() - begin;
return duration < delay
? Promise.delay(delay - duration, v).then(checkTime);
: v;
});
};
并在promiseRateLimit
中使用。