Skip to content

Commit

Permalink
promises -> series
Browse files Browse the repository at this point in the history
  • Loading branch information
yoshuawuyts committed Jul 21, 2015
1 parent e10634a commit abee41f
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 77 deletions.
56 changes: 42 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@

Action dispatcher for unidirectional data flows. Provides action composition
and checks for circular dependencies with a small interface of only 3
functions. Uses promises under the hood, works with
[any es6 compliant promise library](https://www.npmjs.com/package/any-promise).
functions.

## Installation
```sh
Expand All @@ -21,7 +20,8 @@ const barracks = require('barracks')
const dispatcher = barracks()
const store = []

dispatcher.on('insert', (data) => store.push(data.name))
dispatcher.on('error', err => console.log(err))
dispatcher.on('insert', data => store.push(data.name))
dispatcher.on('upsert', (data, wait) => {
const index = store.indexOf(data.prevName)
if (index !== -1) return wait('insert')
Expand All @@ -37,26 +37,54 @@ dispatcher('upsert', {name: 'Loki', newName: 'Tobi'})
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.
Register a new action. Checks for circular dependencies when dispatching. The
callback receives the passed in data and a `wait(actions[, cb])` 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?
## Events
### .on('error', cb(err))
Handle errors. Warns if circular dependencies exists.

## FAQ
### What is an "action dispatcher"?
An action dispatcher gets data from one place to another without tightly
coupling the code. The best known use case for this is in the `flux` pattern.
Say you want to update a piece of data (for example a user's name), instead of
directly calling the update logic inside the view the action calls a function
that updates the user's name for you. Now all the views that need to update a
user's name can call the same action and pass in the relevant data. This
pattern tends to make views more robust and easier to maintain.

### Why did you build this?
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.
takes node's familiar `EventEmitter` interface and adapts it for use as an
action dispatcher.

### I want to start using barracks, but I'm not sure how to use it
That's fine, that means this readme needs to be improved. Would you mind
opening an [issue](https://github.com/yoshuawuyts/barracks/issues) and explain
what you don't understand? I want `barracks` to be comprehensive for
developers of any skill level, so don't hesitate to ask questions if you're
unsure about something.

### Why didn't you include feature X?
An action dispatcher doesn't a lot of features to pass a message from A to B.
`barracks` was built for flexibility. If you feel you're repeating yourself a
lot with `barracks` or are missing a feature, feel free to wrap and extend it
however you like.

## Notes
Because promises are being used under the hood errors cannot be `throw`n,
instead errors are propagated down through a `.on('error')` listener or
a Promise `.catch()` clause at the invocation site.
### What data store do you recommend using with barracks?
In flux it's common to store your application state in a data store.I think a
data store should be immutable, single-instance and allow data access through
cursors / lenses. At the moment of writing I haven't found a data store I'm
pleased with, so I'll probably end up writing one in the near future.

## See Also
- [wayfarer](https://github.com/yoshuawuyts/wayfarer) - composable trie based route
Expand Down
87 changes: 52 additions & 35 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// const debug = require('debug')('barracks')
// const isPromise = require('is-promise')
const Promise = require('any-promise')
const series = require('run-series')
const assert = require('assert')

module.exports = dispatcher
Expand All @@ -27,57 +26,75 @@ function dispatcher () {
return emit
}

// call an action and execute the corresponding callback
// call an action and
// execute the corresponding callback
// (str, obj?) -> prom
function emit (action, data) {
assert.ok(Array.isArray(actions[action]), 'action exists')
const fn = actions[action][0]
const stack = [action]
return new Promise(function (resolve) {
resolve()
})
.then(function () {
return fn(data, wait)
})
.then(function () {})
.catch(function (e) {
const handler = actions.error
if (!handler) throw e
handler(e)
})

return fn(data, wait)

// internal 'wait()' function
// ([str], fn) -> prom
function wait (action, cb) {
action = Array.isArray(action) ? action : [action]
cb = cb || function () {}

const arr = action.map(function (act) {
assert.ok(Array.isArray(actions[act]), 'action exists')
return {
action: actions[act][0],
name: act
}
// retrieve actions
const arr = action.map(function (name) {
const actExists = Array.isArray(actions[name])
assert.ok(actExists, 'action ' + name + ' does not exist')
const fn = actions[name][0]
return createDone(name, fn)
})

const chain = arr.reduce(function (prev, curr) {
return prev
.then(push)
.then(pop)
return series(arr, cb)

function push () {
const index = stack.indexOf(curr.name)
assert.equal(index, -1, 'circular dependency detected')
stack.push(curr.name)
return curr.action(data, wait)
}
// wrap an action with a `done()` method
// for usage in `series()`
// (str, fn(any, (str, fn)) -> fn
function createDone (name, fn) {
return function (done) {
const index = stack.indexOf(name)
if (index !== -1) return emitErr('circular dependency detected')
stack.push(name)

function pop () {
stack.pop()
if (fn.length === 2) return fn(data, retFn)
fn(data)
end()

// execute `wait()` and `done()`
// to both delegate new `series()` calls
// handle cb's, and exit once done
// (str, fn) -> null
function retFn (action, cb) {
if (action) return wait(action, endWrap)
endWrap()

function endWrap () {
if (cb) cb()
end()
}
}

// pop name from stack
// and exit series call
// null -> null
function end () {
stack.pop()
done()
}
}
}, Promise.resolve())
}
}

return chain.then(cb)
// emit an error
// any -> null
function emitErr (err) {
const emitter = actions.error ? actions.error[0] : function () {}
emitter(err)
}
}
}
19 changes: 10 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,37 +1,38 @@
{
"name": "barracks",
"version": "5.0.4",
"description": "An event dispatcher for the flux architecture",
"description": "Action dispatcher for unidirectional data flows",
"main": "index.js",
"scripts": {
"test": "standard && NODE_ENV=test node test",
"test-cov": "standard && NODE_ENV=test istanbul cover test.js"
"test-cov": "standard && NODE_ENV=test istanbul cover test.js",
"watch": "watch 'npm t'"
},
"repository": "yoshuawuyts/barracks",
"keywords": [
"dispatcher",
"flux",
"action",
"minimal",
"emitter",
"event",
"react",
"react-component"
],
"license": "MIT",
"devDependencies": {
"coveralls": "~2.10.0",
"istanbul": "~0.2.10",
"make-lint": "^1.0.1",
"standard": "^4.5.4",
"tape": "^3.0.3"
"tape": "^3.0.3",
"watch": "^0.16.0"
},
"dependencies": {
"any-promise": "^0.1.0",
"assertf": "^1.0.0",
"debug": "^2.0.0",
"is-promise": "^2.0.0",
"promise-each": "^2.1.1",
"run-series": "^1.1.2"
},
"files": [
"HISTORY.md",
"README.md",
"LICENSE",
"index.js"
]
Expand Down
27 changes: 8 additions & 19 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ test('.emit() should call a function', function (t) {
t.plan(2)
const d = barracks()
d.on('foo', function () {
t.pass()
t.pass('is called')
})
d('foo')
d.on('bar', function (data) {
Expand All @@ -43,12 +43,12 @@ test('wait() should be able to chain 2 functions', function (t) {
const d = barracks()
d.on('foo', function (data, wait) {
t.pass('is called')
return wait('bar')
t.equal(typeof wait, 'function')
wait('bar')
})
d.on('bar', function (data, wait) {
d.on('bar', function (data) {
t.pass('is called')
t.equal(data, 'hello me')
t.equal(typeof wait, 'function')
})
d('foo', 'hello me')
})
Expand All @@ -68,6 +68,7 @@ test('wait should be able to chain 4 functions', function (t) {
t.pass('is called')
t.equal(data, 'hello me')
t.equal(typeof wait, 'function')
wait()
}
})

Expand All @@ -80,7 +81,7 @@ test('wait() should call a callback on end', function (t) {
t.equal(i, 1)
})
})
d.on('bar', function (data, wait) {
d.on('bar', function (data) {
t.pass('is called')
i++
})
Expand Down Expand Up @@ -121,7 +122,7 @@ test('wait() should be able to call functions 4 levels deep', function (t) {
d('foo', 'hello me')
})

test('.emit() should throw on circular dependencies', function (t) {
test('.emit() should emit `error` on circular dependencies', function (t) {
t.plan(1)
const d = barracks()
d.on('bin', function (data, wait) {
Expand All @@ -130,20 +131,8 @@ test('.emit() should throw on circular dependencies', function (t) {
d.on('bar', function (data, wait) {
return wait('bin')
})
d('bin')
.catch(function (e) {
t.equal(e.message, 'circular dependency detected')
})
})

test(".emit() should expose `.on('error')`", function (t) {
t.plan(1)
const d = barracks()
d.on('bin', function (data, wait) {
throw new Error('derp')
})
d.on('error', function (err) {
console.log(err)
t.equal(err, 'circular dependency detected')
})
d('bin')
})
Expand Down

0 comments on commit abee41f

Please sign in to comment.