Comparing version 2.1.3 to 3.0.0
89
index.js
@@ -7,2 +7,34 @@ var is = require('./is') | ||
function wrapGenerator (gen) { | ||
return function (ctx, callback) { | ||
function step (err, res) { | ||
// generator step | ||
try { | ||
var state = err ? iter.throw(err) : iter.next(res) | ||
if (state.done) { | ||
// generator done, callback middleware | ||
callback(null, state.value) | ||
} else if (state.value && is.function(state.value.then)) { | ||
// thenable | ||
state.value.then(function (res) { | ||
step(null, res) | ||
}, function (err) { | ||
step(err || true) | ||
}) | ||
} | ||
} catch (err) { | ||
// catch err, break generator | ||
callback(err) | ||
} | ||
} | ||
function next (err, res) { | ||
process.nextTick(function () { | ||
step(err, res) | ||
}) | ||
} | ||
var iter = gen.call(this, ctx, next) | ||
step() | ||
} | ||
} | ||
// ginga use method | ||
@@ -42,3 +74,5 @@ function use () { | ||
if (is.function(args[i])) { | ||
this._hooks[name].push(args[i]) | ||
this._hooks[name].push( | ||
is.generator(args[i]) ? wrapGenerator(args[i]) : args[i] | ||
) | ||
} else if (is.array(args[i])) { | ||
@@ -54,3 +88,2 @@ // use('a', [fn1, fn2, fn3]) | ||
} | ||
return this | ||
@@ -77,4 +110,8 @@ } | ||
if (!is.function(invoke)) invoke = null | ||
else if (is.generator(invoke)) invoke = wrapGenerator(invoke) | ||
var pre = args | ||
var pre = args.map(function (fn) { | ||
if (!is.function(fn)) throw new Error('Middleware must be a function') | ||
return is.generator(fn) ? wrapGenerator(fn) : fn | ||
}) | ||
@@ -115,11 +152,5 @@ // define scope method | ||
function next (cb) { | ||
if (is.function(cb)) { | ||
return function (err, result) { | ||
if (err) return next(err) | ||
cb(result) | ||
next() | ||
} | ||
} | ||
if (arguments.length > 0) { | ||
function next (err, res) { | ||
if (err || index === size) { | ||
// callback when err or end of pipeline | ||
if (callback) callback.apply(self, arguments) | ||
@@ -129,33 +160,19 @@ var args = ['end'] | ||
ctx.emit.apply(ctx, args) | ||
return | ||
} | ||
if (index < size) { | ||
} else if (index < size) { | ||
var fn = pipe[index] | ||
var argsLen = fn.length | ||
index++ | ||
var val = argsLen > 2 | ||
? fn.call(self, ctx, resolve, reject) | ||
: fn.call(self, ctx, next) | ||
var val = fn.call(self, ctx, next) | ||
if (val && is.function(val.then)) { | ||
// thenable | ||
val.then(function () { | ||
next() | ||
}).catch(reject) | ||
} else if (argsLen < 2) { | ||
// args without next() | ||
next() | ||
val.then(function (res) { | ||
next(null, res) | ||
}, function (err) { | ||
next(err || true) | ||
}) | ||
} else if (fn.length < 2) { | ||
// args without next(), not thenable | ||
next(null, val) | ||
} | ||
} else { | ||
// trigger empty callback if no more pipe | ||
next(null) | ||
} | ||
} | ||
function reject (err) { | ||
next(err || true) | ||
} | ||
function resolve (res) { | ||
next(null, res) | ||
} | ||
@@ -162,0 +179,0 @@ if (callback) { |
@@ -27,1 +27,2 @@ var is = module.exports | ||
} | ||
is.generator = require('is-generator-function') |
{ | ||
"name": "ginga", | ||
"version": "2.1.3", | ||
"description": "Middleware framework for JavaScript functions", | ||
"version": "3.0.0", | ||
"description": "Middleware framework for async functions using callback, promise or generator", | ||
"scripts": { | ||
@@ -23,3 +23,4 @@ "test": "set -e; standard; for t in test/*.js; do node $t; done", | ||
"dependencies": { | ||
"pinkie-promise": "^1.0.0" | ||
"is-generator-function": "^1.0.3", | ||
"pinkie-promise": "^2.0.0" | ||
}, | ||
@@ -26,0 +27,0 @@ "devDependencies": { |
# Ginga.js | ||
Ginga is a utility module that enables a middleware based (express inspired), modular architecture for creating asynchronous JavaScript function. Supports both callback and [promise](https://github.com/floatdrop/pinkie-promise). | ||
Middleware based control flow for defining async JavaScript methods using callback, promise or generator. | ||
@@ -28,14 +28,13 @@ [![Build Status](https://travis-ci.org/cshum/ginga.svg?branch=master)](https://travis-ci.org/cshum/ginga) | ||
#### app.define(name, [pre...], invoke) | ||
Creates an async `name` method that supports both callback and promise. See examples below. | ||
#### app.use(name, [hook...]) | ||
`define()` and `use()` a method with `pre`, `hook`, `invoke` middleware functions. | ||
`pre` middlewares initiate and batch operations where `invoke` commits result. | ||
`hook` can be mounted for additional validations or amendments. | ||
Inject additional middleware between `pre` and `invoke` of method `name`. See examples below. | ||
Ginga method supports both callback and [promise](https://github.com/floatdrop/pinkie-promise). | ||
### 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: | ||
Upon calling a method, Ginga method goes through a sequence of middleware functions with following arguments: | ||
@@ -45,7 +44,9 @@ * `ctx` - context event emitter object: | ||
* A middleware can make changes to context object, or access changes made by previous middleware. | ||
* Emits end event `ctx.on('end', fn)` on callback with error and result arguments. | ||
* `next` - callback function: | ||
* `next()` to pass control to the next middleware. | ||
* `next(err, result)` to end the sequence and callback with error or result. | ||
* 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 | ||
```js | ||
@@ -57,13 +58,15 @@ var ginga = require('ginga') | ||
app.define('test', function (ctx, next) { | ||
ctx.logs = ['pre'] | ||
next() | ||
}, function (ctx, done) { | ||
setTimeout(function () { | ||
ctx.logs = ['pre'] | ||
next() // next middleware callback | ||
}, 1000) | ||
}, function (ctx) { | ||
// not passing next argument: treated as synchronous call | ||
ctx.logs.push('invoke') | ||
done(null, ctx.logs) | ||
return ctx.logs // returns value of the end of middleware sequence | ||
}) | ||
// hook | ||
app.use('test', function (ctx, next) { | ||
app.use('test', function (ctx) { | ||
ctx.logs.push('hook') | ||
setTimeout(next, 10) // async next | ||
}) | ||
@@ -76,26 +79,50 @@ | ||
// method call with promise | ||
app.test().then(function (res) { | ||
console.log(res) // ['pre', 'hook', 'invoke'] | ||
}) | ||
``` | ||
shortcuts for callback functions: | ||
#### Promise | ||
By returning promise, value will be resolved before passing to next middleware or returning result. Promise reject ends the middleware sequence. | ||
```js | ||
function (ctx, next) { | ||
task(function (err, res) { | ||
if (err) return next(err) | ||
var ginga = require('ginga') | ||
var app = ginga() | ||
// define method | ||
app.define('test', function (ctx) { | ||
return fnAsync().then(function (data) { | ||
// do stuff | ||
next() | ||
}) | ||
} | ||
// equivalent to | ||
function (ctx, next) { | ||
task(next(function (res) { | ||
// do stuff | ||
})) | ||
} | ||
}, function (ctx) { | ||
// returns result from last promise resolve | ||
return fn2Async() | ||
}) | ||
// method call with promise | ||
app.test().then(...).catch(...) | ||
``` | ||
#### Generator | ||
In ES6 generators, functions can be paused and resumed using the `yield` keyword. | ||
Both promise and callback are 'yieldable' in ginga middleware. | ||
This enables powerful control flow while maintaining compatibility. | ||
```js | ||
var ginga = require('ginga') | ||
var app = ginga() | ||
app.define('test', function * (ctx, next) { | ||
var foo = yield Promise.resolve('bar') // Promise is yieldable | ||
yield setTimeout(next, 100) // callback based function is also yieldable | ||
try { | ||
ctx.key = yield fs.readfile('./foo/bar', next) | ||
} catch (err) { | ||
ctx.key = 'whatever' | ||
} | ||
}, function (ctx) * { | ||
// returns result | ||
return yield db.get(ctx.key) | ||
}) | ||
``` | ||
#### ginga.params([param...]) | ||
@@ -102,0 +129,0 @@ |
@@ -5,4 +5,5 @@ var tape = require('tape') | ||
function invoke (ctx, resolve, reject) { | ||
return resolve(ctx.params) | ||
function invoke (ctx) { | ||
// returns val | ||
return ctx.params | ||
} | ||
@@ -9,0 +10,0 @@ var obj = ginga() |
@@ -6,3 +6,3 @@ var tape = require('tape') | ||
tape('ginga prototype', function (t) { | ||
t.plan(12) | ||
t.plan(8) | ||
@@ -18,8 +18,5 @@ function Clock () { | ||
} | ||
function tick (ctx, next) { | ||
function tick (ctx) { | ||
// no next arg | ||
ctx.logs.push(this._tick) | ||
// resolver function | ||
next(function (result) { | ||
t.equal(result, 167199, 'resolver result') | ||
})(null, 167199) | ||
} | ||
@@ -26,0 +23,0 @@ function tock (ctx) { |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
22091
14
557
243
2
+ Addedis-generator-function@^1.0.3
+ Addedhas-symbols@1.0.3(transitive)
+ Addedhas-tostringtag@1.0.2(transitive)
+ Addedis-generator-function@1.0.10(transitive)
+ Addedpinkie@2.0.4(transitive)
+ Addedpinkie-promise@2.0.1(transitive)
- Removedpinkie@1.0.0(transitive)
- Removedpinkie-promise@1.0.0(transitive)
Updatedpinkie-promise@^2.0.0