uppy/packages/@uppy/utils/src/RateLimitedQueue.js
Renée Kooi bb8b66d918
Use more cancellation-friendly strategy for limit: N uploads (#1736)
* xhr-upload: redo limit option using different strategy

* xhr-upload: fix marking requests as done

* Move duplicate createEventTracker definitions to @uppy/utils

* tus: new cancellation for local uploads

* tus: fix cancelling queued uploads

* tus: fix wrong name

* aws-s3-multipart: smol refactor

* aws-s3-multipart: new cancellation style

* aws-s3: new cancellation

* utils: port limitPromises test to RateLimitedQueue

* timing fix?

* tus: new cancellation on websocket

* xhr-upload: implement cancellation for remote uploads

* aws-s3-multipart: port to new cancellation

* utils: remove limitPromises

* xhr-upload: remove pause/resume code

* xhr-upload: clean up event listeners

* xhr-upload: extract progress timer to separate class

* Move ProgressTimeout class to utils

* utils: update typings

* Fix lint

* tus: make pause/resume respect the rate limiting queue

* tus: try to explain some of the tricky things at play in the Tus#upload method

* aws-s3-multipart: add missing { abort: true }

* aws-s3-multipart: make pause/resume respect the rate limiting queue

* eslintrc.json - fixed eslint warnings for jsdoc

* Revert "eslintrc.json - fixed eslint warnings for jsdoc"

This reverts commit 50b24773ce.

* eslintrc.json - allow indentation in jsdoc comments

* xhr-upload: fix promise return value

* tus: fix promise return value

* tus: add missing `return () => {}`

* utils: typing export type fixes

* tus: add more jsdoc

* companion-client: add missing SocketOptions type

* utils: fix more export typings

* core,companion-client,tus: more typings tweaking

* companion-client: test fix 😩

* companion-client: add type for Socket#open

* tus: fix emit() call

* add local cancellation fuzz....ish? test
2019-09-25 15:21:17 +02:00

115 lines
2.4 KiB
JavaScript

module.exports = class RateLimitedQueue {
constructor (limit) {
if (typeof limit !== 'number' || limit === 0) {
this.limit = Infinity
} else {
this.limit = limit
}
this.activeRequests = 0
this.queuedHandlers = []
}
_call (fn) {
this.activeRequests += 1
let done = false
let cancelActive
try {
cancelActive = fn()
} catch (err) {
this.activeRequests -= 1
throw err
}
return {
abort: () => {
if (done) return
done = true
this.activeRequests -= 1
cancelActive()
this._next()
},
done: () => {
if (done) return
done = true
this.activeRequests -= 1
this._next()
}
}
}
_next () {
if (this.activeRequests >= this.limit) {
return
}
if (this.queuedHandlers.length === 0) {
return
}
// Dispatch the next request, and update the abort/done handlers
// so that cancelling it does the Right Thing (and doesn't just try
// to dequeue an already-running request).
const next = this.queuedHandlers.shift()
const handler = this._call(next.fn)
next.abort = handler.abort
next.done = handler.done
}
_queue (fn) {
const handler = {
fn,
abort: () => {
this._dequeue(handler)
},
done: () => {
throw new Error('Cannot mark a queued request as done: this indicates a bug')
}
}
this.queuedHandlers.push(handler)
return handler
}
_dequeue (handler) {
const index = this.queuedHandlers.indexOf(handler)
if (index !== -1) {
this.queuedHandlers.splice(index, 1)
}
}
run (fn) {
if (this.activeRequests < this.limit) {
return this._call(fn)
}
return this._queue(fn)
}
wrapPromiseFunction (fn) {
return (...args) => new Promise((resolve, reject) => {
const queuedRequest = this.run(() => {
let cancelError
fn(...args).then((result) => {
if (cancelError) {
reject(cancelError)
} else {
queuedRequest.done()
resolve(result)
}
}, (err) => {
if (cancelError) {
reject(cancelError)
} else {
queuedRequest.done()
reject(err)
}
})
return () => {
cancelError = new Error('Cancelled')
}
})
})
}
}