Ginga.js
Middleware based control flow for defining async JavaScript methods using callback, promise or generator.
npm install ginga
ginga([object])
Initialise ginga
var ginga = require('ginga')
var obj = ginga()
var app = {}
ginga(app)
function App () { }
ginga(App.prototype)
Method and Hook
app.define(name, [pre...], invoke)
Creates an async name
method that supports both callback and promise. See examples below.
app.use(name, [hook...])
Inject additional middleware between pre
and invoke
of method name
. See examples below.
Middleware
Middleware turns asynchronous functions into encapsulated, reusable set of building blocks.
Upon calling a method, Ginga method goes through a sequence of middleware functions with following arguments:
ctx
- context event emitter object:
- Maintains state throughout the method call, while encapsulated from
this
object. - A middleware can make changes to context object, or access changes made by previous middleware.
- Emits
end
event with error and result arguments.
next
- optional stepping function using callback, which ends the sequence if callback with error argument.
Ginga middleware can be created using callback, promise or generator, interchangeably:
Callback
var ginga = require('ginga')
var app = ginga()
app.define('test', function (ctx, next) {
setTimeout(function () {
ctx.logs = ['pre']
next()
}, 1000)
}, function (ctx) {
ctx.logs.push('invoke')
return ctx.logs
})
app.use('test', function (ctx) {
ctx.logs.push('hook')
})
app.test(function (err, res) {
console.log(res)
})
Promise
By returning promise, value will be resolved before passing to next middleware or returning result. Promise reject ends the middleware sequence.
var ginga = require('ginga')
var app = ginga()
app.define('test', function (ctx) {
return fnAsync().then(function (data) {
})
}, function (ctx) {
return fn2Async()
})
app.test().then(...).catch(...)
Generator
In ES6 generators, functions can be paused and resumed using the yield
keyword.
Using caco, both promise and callback are 'yieldable' in ginga middleware.
This enables powerful control flow while maintaining compatibility.
var ginga = require('ginga')
var app = ginga()
app.define('test', function * (ctx, next) {
var foo = yield Promise.resolve('bar')
yield setTimeout(next, 100)
try {
ctx.key = yield fs.readfile('./foo/bar', next)
} catch (err) {
ctx.key = 'whatever'
}
}, function (ctx) * {
return yield db.get(ctx.key)
})
ginga.params([param...])
Ginga built in ginga.params
middleware for parsing method arguments. Supports optional parameters and type-checking.
param
is string in form of
name[:type][?]
name
- name of parameter mapped from argumenttype
type checking (optional): string
, boolean
, function
, number
, date
, regexp
, object
, array
, case insensitive.?
- optional parameter.
var ginga = require('ginga')
var params = ginga.params
var app = ginga()
app.define('test', params('a', 'b:number?', 'c:string?'), function (ctx) {
return ctx.params
})
app.test('s', 1, function (err, res) {
console.log(res)
})
app.test('s', 't', function (err, res) {
console.log(res)
})
app.test(function (err, res) {
console.log(err)
})
Plugin
app.use(plugin)
app.use
also accepts Ginga object as plugin. This will mount hooks into the main app.
var ginga = require('ginga')
var app = ginga()
app.define('test', function (ctx) {
ctx.logs = ['pre']
}, function (ctx) {
ctx.logs.push('invoke')
return ctx.logs
})
var plugin = ginga()
plugin.use('test', function (ctx, next) {
ctx.logs.push('plugin')
next()
})
app.use(plugin)
app.test(function (err, res) {
console.log(res)
})
Inheritance
By initialising Ginga with prototype mixin, hooks are also inherited in prototype chain:
var ginga = require('ginga')
function App () { }
var A = ginga(App.prototype)
A.define('test', function (ctx, next) {
ctx.logs = ['pre']
next()
}, function (ctx, done) {
ctx.logs.push('invoke')
done(null, ctx.logs)
})
var a1 = new App()
var a2 = new App()
A.use('test', function (ctx) {
ctx.logs.push('A hook')
})
a1.use('test', function (ctx) {
ctx.logs.push('a1 hook')
})
a2.use('test', function (ctx) {
ctx.logs.push('a2 hook')
})
a1.test(function (err, res) {
console.log(res)
})
a2.test(function (err, res) {
console.log(res)
})
License
MIT