问题为承诺率限制功能创建有效的测试用例

Tho*_*ggi 6 javascript promise bluebird

我正在尝试为promiseRateLimit下面的函数创建一个有效的测试用例.该promiseRateLimit函数的工作方式是使用a 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()
    })
  }
}
Run Code Online (Sandbox Code Playgroud)

以下是该功能的实例.

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

该example承诺需要50ms运行和promiseRateLimit功能将只允许1答应每100ms.所以承诺之间的间隔应该大于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')
})
Run Code Online (Sandbox Code Playgroud)

我创建了一个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
}
Run Code Online (Sandbox Code Playgroud)

问题是上面的测试有时会返回一个低于的测试间隔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)
})
Run Code Online (Sandbox Code Playgroud)

即使有了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>)
Run Code Online (Sandbox Code Playgroud)

Ber*_*rgi 1

setTimeout(内部使用Promise.delay)不保证准确的计时,它只确保回调函数不会在给定的超时到期之前被调用。实际时间将取决于机器负载、事件循环的速度以及可能的其他因素。

事实上,Node.js 文档仅指出

该命令callback可能不会在精确的delay毫秒内被调用。Node.js 不保证回调触发的确切时间,也不保证回调的顺序。回调将尽可能接近指定的时间被调用。

在您的测试中会发生的情况是,Promise.delay(50)有时需要超过 50 毫秒(虽然不是很多,但仍然如此),并且当下一个日志Promise.delay(50)更准时时,与以下日志的差异可能会变得小于 100 毫秒。

如果您只是立即记录函数的调用时间,而不是在大约example50 毫秒的人为延迟之后,您应该能够减轻这种影响:

const example = v => {
  timeLog.push(new Date);
  runCount++;
  return Promise.delay(50, v)
};
Run Code Online (Sandbox Code Playgroud)

为了解决 100 毫秒超时本身的不准确性,最简单的解决方案是给它一些可能 5% 的余地(在您的情况下为 5 毫秒):

const invalidIntervals = filter(intervals, (interval) => interval < 100 * .95)
t.true(totalTime >= 400 * .95)
Run Code Online (Sandbox Code Playgroud)

如果您想绝对确保延迟不会太短,您可以编写自己的函数:

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

并在 中使用它promiseRateLimit。