graphbrainz/src/rate-limit.js

103 lines
2.6 KiB
JavaScript
Raw Normal View History

2016-12-03 07:59:19 +00:00
const debug = require('debug')('graphbrainz:rate-limit')
2016-08-08 07:54:06 +00:00
export default class RateLimit {
2016-11-26 01:38:32 +00:00
constructor ({
limit = 1,
period = 1000,
concurrency = limit || 1,
defaultPriority = 1
} = {}) {
this.limit = limit
this.period = period
this.defaultPriority = defaultPriority
this.concurrency = concurrency
2016-08-08 07:54:06 +00:00
this.queues = []
this.numPending = 0
this.periodStart = null
this.periodCapacity = this.limit
this.timer = null
this.pendingFlush = false
2016-12-03 07:59:19 +00:00
this.prevTaskID = null
}
nextTaskID (prevTaskID = this.prevTaskID) {
const id = (prevTaskID || 0) + 1
this.prevTaskID = id
return id
2016-08-08 07:54:06 +00:00
}
enqueue (fn, args, priority = this.defaultPriority) {
priority = Math.max(0, priority)
return new Promise((resolve, reject) => {
const queue = this.queues[priority] = this.queues[priority] || []
2016-12-03 07:59:19 +00:00
const id = this.nextTaskID()
debug(`Enqueuing task. id=${id} priority=${priority}`)
queue.push({ fn, args, resolve, reject, id })
2016-08-08 07:54:06 +00:00
if (!this.pendingFlush) {
this.pendingFlush = true
process.nextTick(() => {
this.pendingFlush = false
this.flush()
})
}
})
}
dequeue () {
let task
for (let i = this.queues.length - 1; i >= 0; i--) {
const queue = this.queues[i]
if (queue && queue.length) {
task = queue.shift()
}
if (!queue || !queue.length) {
this.queues.length = i
}
if (task) {
break
}
}
return task
}
flush () {
2016-08-20 05:59:32 +00:00
if (this.numPending < this.concurrency && this.periodCapacity > 0) {
2016-08-08 07:54:06 +00:00
const task = this.dequeue()
if (task) {
2016-12-03 07:59:19 +00:00
const { resolve, reject, fn, args, id } = task
2016-08-08 07:54:06 +00:00
if (this.timer == null) {
const now = Date.now()
let timeout = this.period
if (this.periodStart != null) {
const delay = now - (this.periodStart + timeout)
if (delay > 0 && delay <= timeout) {
timeout -= delay
}
}
this.periodStart = now
this.timer = setTimeout(() => {
this.timer = null
this.periodCapacity = this.limit
this.flush()
}, timeout)
}
this.numPending += 1
this.periodCapacity -= 1
const onResolve = (value) => {
this.numPending -= 1
resolve(value)
this.flush()
}
const onReject = (err) => {
this.numPending -= 1
reject(err)
this.flush()
}
2016-12-03 07:59:19 +00:00
debug(`Running task. id=${id}`)
2016-08-08 07:54:06 +00:00
fn(...args).then(onResolve, onReject)
this.flush()
}
}
}
}