Created
August 15, 2018 08:56
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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