Skip to content

Instantly share code, notes, and snippets.

@aronanda
Last active November 24, 2022 01:29
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aronanda/d31bb47918145a5aace6005f172e035d to your computer and use it in GitHub Desktop.
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
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
}
}
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) })
}
}
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())
}
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
})
@kindziora
Copy link

thanks for the nice work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment