Skip to content

Commit

Permalink
docs: change to new api
Browse files Browse the repository at this point in the history
  • Loading branch information
yoshuawuyts committed Jul 13, 2015
1 parent d77d659 commit 1a47faa
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 285 deletions.
183 changes: 35 additions & 148 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,166 +4,53 @@
[![Test coverage][coveralls-image]][coveralls-url]
[![Downloads][downloads-image]][downloads-url]

Event dispatcher for the [flux architecture][flux]. Provides event composition
through `this.waitFor()` and checks for circular dependencies with a small
interface of only 3 functions.

```
╔═════╗ ╔════════════╗ ╔════════╗ ╔═════════════════╗
║ API ║<──────>║ Middleware ║──────>║ Stores ║──────>║ View Components ║
╚═════╝ ╚════════════╝ ╚════════╝ ╚═════════════════╝
^ │
│ │
╔════════════╗ │
║ Dispatcher ║ │
╚════════════╝ │
^ │
└────────────────────────────────────────┘
```
Action dispatcher for unidirectional data flows. Provides event composition
and checks for circular dependencies with a small interface of only 3 functions.

## Installation
```sh
npm install barracks
$ npm install barracks
```

## Overview
## Usage
````js
var barracks = require('barracks');
const barracks = require('barracks')

// Initialize dispatcher.
const dispatcher = barracks()
const store = []

var dispatcher = barracks({
users: {
add: function(next) {
console.log(user + ' got added');
next();
}
},
courses: {
get: function(next) {
console.log('Get ' + this.payload);
next();
},
set: function(next) {
console.log('Set ' + this.payload);
next();
}
}
});
dispatcher.on('users.insert', (data) => store.push(data.name))
dispatcher.on('users.update', (data) => {
store[store.indexOf(data.prevName)] = data.newName
})

// Dispatch an event.

dispatcher('users_add', 'Loki');
// => 'Loki got added'
dispatcher('users.insert', {name: 'Loki'})
dispatcher('users.update', {prevName: 'Loki', newName: 'Tobi'})
````

## API
#### dispatcher = barracks(actions)
Initialize a new `barracks` instance. Returns a function.
```js
// Initialize without namespaces.

var dispatcher = barracks({
user: function() {},
group: function() {}
});

// Initialize with namespaces.

var dispatcher = barracks({
users: {
add: function() {},
remove: function() {}
},
courses: {
get: function() {},
put: function() {}
}
});
```

#### dispatcher(action, data)
`barracks()` returns a dispatcher function which can be called to dispatch an
action. By dispatching an action you call the corresponding function from
the dispatcher and pass it data. You can think of it as just calling a
function.

In order to access namespaced functions you can delimit your string with
underscores. So to access `courses.get` you'd dispatch the string `courses_get`.
````js
// Call a non-namespaced action.
dispatcher('group', [123, 'hello']);

// Call a namespaced action.
dispatcher('users_add', {foo: 'bar'});
````

#### ctx.waitFor(action)
Execute another function within the dispatcher before proceeding. Registered
callbacks are always bound to the scope of the dispatcher, so you can just
call `this.waitFor` to access the function from within a registered callback.
```js
var dispatcher = barracks({
init: function(next) {
console.log('1');
this.waitFor(['add', 'listen'], function() {
console.log('4');
next();
});
},
add: function(next) {
setTimeout(function() {
console.log('2');
done();
}, 10);
},
listen: function(next) {
console.log('3');
next();
}
});

dispatcher('init');
// => 1 2 3
```

#### ctx.payload
`this.payload` contains the data provided by `dispatcher()`.
```js
var dispatcher = barracks({
init: function(next) {
console.log(this.payload);
}
});

dispatcher('init', 'fooBar');
// => 'fooBar'
```

#### ctx.locals=
`this.locals` is shared between all (delegated) function calls and acts as the
location to share data between function calls. For example when you retrieve
a token from a store and want to make it available to all subsequent functions.

The payload provided by `dispatcher()` is available under `this.locals.payload`.
```js
var dispatcher = barracks({
add: function(next) {
this.locals.token = 'asdf12345';
next();
});
},
fetch: function(next) {
this.waitFor(['add'], function() {
console.log(this.locals.token);
next();
});
}
});

dispatcher('fetch');
// => 'asdf12345'
```
### dispatcher = barracks()
Initialize a new `barracks` instance.

### dispatcher.on(action, cb(data, wait))
Register a new action. Checks for circular dependencies when dispatching.
The callback receives the passed in data and a `wait()` function that can be
used to call other actions internally. `wait()` accepts a single action or an
array of actions and an optional callback as the final argument.

### dispatcher(event[, data])
Call an action and execute the corresponding callback. Alias:
`dispatcher.emit(event[, data])`.

## Why?
Passing messages around should not be complicated. Many `flux` implementations
casually throw around framework specific terminology making new users feel
silly for not following along. I don't like that. `barracks` is a package that
takes the familiar `EventEmitter` interface and adapts it for use as an action
dispatcher.

## See Also
- [wayfarer](https://github.com/yoshuawuyts/wayfarer) - composable trie based route

## License
[MIT](https://tldrlegal.com/license/mit-license)
Expand Down
148 changes: 11 additions & 137 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,145 +1,19 @@
const debug = require('debug')('barracks')
const assert = require('assertf')
const async = require('async')
// const debug = require('debug')('barracks')
// const assert = require('assertf')
// const async = require('async')

module.exports = Dispatcher
module.exports = dispatcher

// initialize the dispatcher with actions
// obj -> fn
function Dispatcher (actions) {
if (!(this instanceof Dispatcher)) return new Dispatcher(actions)
// initialize a new barracks instance
// null -> obj
function dispatcher () {

assert(actions, "an 'actions' object should be passed as an argument")
assert.equal(typeof actions, 'object', 'actions should be an object')
_assertActionsObject(actions)
emit.on = on
return emit

this.locals = {}
this.payload = null

this._current = []
this._isPending = {}
this._isHandled = {}
this._actions = actions
this._isDispatching = false

return this.dispatch.bind(this)
}

// dispatch event to stores
// str, obj|[obj] -> fn
Dispatcher.prototype.dispatch = function (action, payload) {
assert.equal(typeof action, 'string', "action '%s' should be a string", action)
assert(!this._isDispatching, "cannot dispatch '%s' in the middle of a dispatch", action)

this._current.push(action)
this._isDispatching = true

this._isPending = {}
this._isPending[action] = true

this._isHandled = {}
this._isHandled[action] = false

this.locals = {}
this.payload = payload

try {
const fn = _getAction.call(this, action)
} catch (e) {
_stopDispatching.call(this)
throw e
function on (action, cb) {
}

debug("dispatch '%s'", action)
fn.call(this, _stopDispatching.bind(this))
}

// expose a delegation method to the registered
// actions. Calls `async.series()` under the hood
// str|[str], fn -> null
Dispatcher.prototype.waitFor = function (actions, done) {
done = done || function () {}
assert.equal(typeof done, 'function', 'callback should be a function')

actions = Array.isArray(actions) ? actions : [actions]
const ctx = this

const arr = actions.map(function (action) {
const fn = _getAction.call(ctx, action)
const nwFn = _thunkify.call(ctx, fn, action)
return nwFn.bind(ctx)
})

const nwArr = arr.concat(done.bind(this))
async.series(nwArr)
}

// deep assert the actions object
// obj -> null
function _assertActionsObject (actions) {
Object.keys(actions).forEach(function (key) {
const action = actions[key]
if ('object' == typeof action) return _assertActionsObject(action)
assert.equal(typeof action, 'function', 'action should be a function')
})
}

// wrap function to set
// `this._isHandled[action]` on end
// fn, fn -> fn
function _thunkify (fn, action) {
return function (done) {
try {
assert.equal(typeof action, 'string', '.waitFor(): requires a string or array of strings')
if (this._isPending[action]) {
assert(this._isHandled[action], "circular dependency detected while waiting for '%s'", action)
}
} catch(e) {
_stopDispatching.call(this)
throw e
}

this._isPending[action] = true
this._isHandled[action] = false

function fin () {
this._current.pop()
this._isHandled[action] = true
done()
}

this._current.push(action)
debug("'%s' -> '%s'", this._current[this._current.length - 2], action)
fn(fin.bind(this))
function emit (event, data) {
}
}

// get the dispatched action recursively
// [str], str -> fn
function _getAction (action, arr, index) {
arr = arr || action.split('_')
index = index || 0
const val = arr[index]

if ('object' == typeof val) return _getAction.call(this, action, arr, index++)

var fn = this._actions
arr.forEach(function (obj, i) {
assert(fn[arr[i]], "action '%s' is not registered", action)
fn = fn[arr[i]]
})

assert.equal(typeof fn, 'function', "action '%s' is not registered", action)
return fn.bind(this)
}

// reset internal state
// null -> null
function _stopDispatching () {
this._isDispatching = false
this._isPending = {}
this._isHandled = {}
this.payload = null
this._current = []
this.locals = {}
}

0 comments on commit 1a47faa

Please sign in to comment.