Skip to content

Instantly share code, notes, and snippets.

@dannycroft
Created August 15, 2018 08:56
Show Gist options
  • Save dannycroft/39ec2e100e4fa7d19f12f12d3c000c93 to your computer and use it in GitHub Desktop.
Save dannycroft/39ec2e100e4fa7d19f12f12d3c000c93 to your computer and use it in GitHub Desktop.
RequestQueue to ensure that only a single request is executing at a time.
import EventEmitter from 'events';
import onFinished from 'on-finished';
/*
* RequestQueue to ensure that only a single request is executing at a time.
*
* This middleware intercepts requests as they come in by delaying executing of
* next() until previous requests finish processing. This complements external
* server configuration via haproxy or similar that restricts concurrent
* requests. This per-process queue allows an application level guarantee of
* mutual exclusion of requests. This allows that behavior to be depended
* upon, allowing for safe (but careful) use of global state. Additionally,
* this allows for lifecycle hooks to be added for the periods when no request
* is currently executing, before or after the request has been run. These are
* ideal points to install behavior to reset global state or perform actions
* against the server at a "clean state" point in time.
*/
export default class RequestQueue extends EventEmitter {
constructor() {
super();
this.queue = [];
this.current = null;
this.outerMiddleware = this.outerMiddleware.bind(this);
this.innerMiddleware = this.innerMiddleware.bind(this);
this.finishCurrent = this.finishCurrent.bind(this);
}
process() {
if (!this.current) {
this.current = this.queue.shift();
this.emit('queueLength', this.queue.length);
if (this.current) {
this.emit('beforeRequest');
this.current.start();
}
} else {
this.emit('queueLength', this.queue.length);
}
}
/*
* Outer middleware must be the very first middleware installed on the app.
* This intercepts and begins queueing the request.
*/
outerMiddleware(req, res, next) {
const job = { req, res, start: next };
this.push(job);
}
/*
* Inner middleware must be last middleware installed before endpoints. This
* is only necessary because on-finished executes its callbacks in the order
* in which they were installed. We need this to be innermost so that we
* advance the queue only after the request and all other on-finished
* callbacks complete.
*
* Not adding this middleware will result in the queue never being drained.
*/
innerMiddleware(req, res, next) {
onFinished(res, this.finishCurrent);
next();
}
push(job) {
this.queue.push(job);
this.process();
}
finishCurrent() {
this.current = null;
this.emit('afterRequest');
this.process();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment