Last active
November 24, 2022 01:29
-
-
Save aronanda/d31bb47918145a5aace6005f172e035d to your computer and use it in GitHub Desktop.
A minimal Javascript Middleware Pattern Implementation with support for custom parameters, chainable use() method and a definable object. Based on https://gist.github.com/darrenscerri/5c3b3dcbe4d370435cfa
This file contains 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
class Middleware { | |
constructor(obj) { | |
obj = obj || this | |
Object.defineProperty(this, '__obj', { value: obj }) | |
Object.defineProperty(this, 'go', { value: function go(...args) { | |
args[args.length - 1].apply(obj, args.slice(0, -1)) | |
}, writable: true }) | |
} | |
use(fn) { | |
this.go = (stack => (...args) => stack(...args.slice(0, -1), () => | |
fn.call(this.__obj, ...args.slice(0, -1), args[args.length - 1] | |
.bind(this.__obj, ...args.slice(0, -1)))))(this.go) | |
return this | |
} | |
} |
This file contains 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
class Middleware { | |
constructor(obj, method = 'go') { | |
let extras = [] | |
const stack = [] | |
const go = async (...args) => { | |
let done = args[args.length - 1] | |
if (typeof done === 'function') { | |
args.pop() | |
if (obj) done = done.bind(obj) | |
} else done = null | |
try { | |
for (let fn of stack) { | |
if (obj) fn = fn.bind(obj) | |
extras = await new Promise((resolve, reject) => { | |
fn(...args.concat(extras), function next(err, ...extras) { | |
if (err) return reject(err) | |
resolve(extras) | |
}) | |
}) | |
} | |
args = args.concat(extras) | |
if (done) done(...args) | |
return args | |
} catch (err) { throw err } | |
} | |
Object.defineProperty(this, method, { value: go }) | |
Object.defineProperty(this, 'use', { value: fn => stack.push(fn) }) | |
} | |
} |
This file contains 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
const mw = new Middleware() | |
mw.use((a, b, next) => { | |
log('calling 1st func', { a, b }) | |
next() | |
}) | |
mw.use((a, b, next) => { | |
log('calling 2nd func', { a, b }) | |
next(null, 'c') | |
}) | |
mw.use((a, b, c, next) => { | |
log('calling 3rd func', { a, b, c }) | |
if (Math.random() > 0.5) | |
next(new Error('uh oh'), c) | |
else | |
next(null, c) | |
}) | |
mw.use((a, b, c, next) => { | |
log('calling 4th func', { a, b, c }) | |
next(null, c) | |
}) | |
try { | |
let result = await mw.go('a', 'b', (a, b, c) => log('done', { a, b, c })) | |
log(result) | |
} catch (err) { | |
log('err', err.toString()) | |
} |
This file contains 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
const middleware = new Middleware(/* define object or defaults to this */) | |
middleware.use(function (req, res, next) { | |
setTimeout(() => { | |
this.hook1 = true | |
req.step++ | |
res.step++ | |
next() | |
}, 10) | |
}) | |
middleware.use(function (req, res, next) { | |
setTimeout(() => { | |
this.hook2 = true | |
req.step++ | |
res.step++ | |
next() | |
}, 10) | |
}).use((req, res, next) => { | |
// chainable | |
setTimeout(() => { | |
req.step++ | |
res.step++ | |
next() | |
}, 10) | |
}) | |
const start = new Date() | |
const req = Object.create(new class Req {}(), { step: { value: 1, enumerable: true, writable: true }}) | |
const res = Object.create(new class Res {}(), { step: { value: 1, enumerable: true, writable: true }}) | |
middleware.go(req, res, function (req, res) { | |
console.log(this) // Middleware { hook1: true, hook2: true } | |
console.log(req) // Req { step: 4 } | |
console.log(res) // Res { step: 4 } | |
console.log(new Date() - start) // around 30ms | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Nice, I see what you did there. Would be good to pass results to the following middlewares. Here's my latest solution that allows that and throws an error if one is present as the first argument of the next function. I used promises instead to make it easier to read (much easier). And I made the object binding optional as you oftentimes want to bind your own functions. Also, the result is returned at the end, regardless of whether you include a final callback in the go method (or rename the go method to something else ;)
And here's a demo of my new version: