chain-builder
Manage an array of functions and execute them in a series with a variety of flows.
Pair with ordering to have advanced ordering of the array of functions.
Some of the features:
- "chain of command" - call functions in series
- the simplest use case is still simple: supply an array of functions, and they'll be called in sequence.
- complex things are also simple such as providing your own context or altering it during an execution, overriding the
this
without using bind()
, and more. - "waterfall" - uses a
context
object which is provided to each function in sequence. accepts one for the initial call (unlike async.waterfall()
). It's different than providing the output to the next function as input, however, it can achieve the same results, and, it's possible to provide an entirely new object to each subsequent function. - "pipeline/filter" - a series of functions which call the next one, can override the input, can do work after the later functions return
- accepts a
done
callback and sends the error or result to it - can pause the chain and use a callback to resume it; which supports asynchronous execution
- can stop the chain early as a success; accepts a
reason
- can fail the chain execution as an error; accepts a
reason
- the context provided to each function is also the this, unless overridden via an option
- the this can be overridden per function via an options object on the function (better than bind because it uses a single
call()
instead of two) - can override the context used by the next function (which can choose to pass that one on, or, allow the default context to be restored), and, can override the default context used by all future functions and is returned in the final result.
- can
disable()
and enable()
the whole chain, or a single function by its index/id/self, or a selection of functions chosen by a function you provide. - can
remove()
a function by its index/id/self, or using the select()
function, or via control.remove()
during a chain execution. - is an EventEmitter with events for: start, pause, resume, stop, fail, add, remove, disable, enable, and done.
Install
npm install chain-builder --save
Table of Contents (JS5)
A. Usage
- Basic
- Simple
- Complex
- Considerations
- Ordering
- Performance
B. Executing a Chain
- Events
- Control
- Flow Styles
- Context or This?
C. Examples
- Stopping Chain
- Pass on a Value via Context
- Thrown Errors
- Pipeline/Filter
- Asynchronous
D. Advanced Contexts
- Two Contexts
- How to specify the this context
- Why not use bind()?
- Control the Shared Context
E. API
- Exported Builder Function
- Chain
* [chain.run()](#api-chainrun)
* [chain.add()](#api-chainadd)
* [chain.remove()](#api-chainremove)
* [chain.disable()](#api-chaindisable)
* [chain.enable()](#api-chainenable)
* [chain.select()](#api-chainselect)
3. Control
* [control.next()](#api-controlnext)
* [control.context()](#api-controlcontext)
* [control.pause()](#api-controlpause)
* [control.stop()](#api-controlstop)
* [control.fail()](#api-controlfail)
* [control.disable()](#api-controldisable)
* [control.remove()](#api-controlremove)
F. MIT License
Usage: Basic
Here is an extremely basic example to show the basic parts working together.
var buildChain = require('chain-builder')
, chain = buildChain()
function fn1() { console.log('simple') }
chain.add(fn1)
var results = chain.run()
results = {
result: true,
context: {},
chain:
}
Usage: Simple
The most commonly used features:
- accessing the
context
via function param or this
- reporting an error from inside a function
- reviewing the final results
function guard(control) {
if (!this.message) {
control.fail('missing message')
}
}
function fn1() {
this.message += ' there'
}
function fn2() {
this.message += ', Bob'
}
function fn3() {
console.log(this.message)
}
var chain = buildChain(fn1, fn2, fn3)
chain.add(fn1, fn2, fn3)
chain.add([ fn1, fn2, fn3 ])
var context = { message: 'Hello' }
, runOptions = { context: context }
, result = chain.run(runOptions)
result = {
result: true,
context: { message:'Hello there, Bob' },
chain:
}
delete context.message
result = chain.run(runOptions)
result = {
result: false,
context: {},
failed: {
reason: 'missing message',
index: 0,
fn: ...
}
, chain:
}
Usage: Complex
Show many advanced features:
- add some functions via the chain builder function
- add an array of functions from a module and then remove one of them
- add another group of functions and then remove multiple of them with a
select()
based on "labels" in their function options - disable a function so it's skipped until we want it to join in
- enable a function which was disabled by default by its provider
- provide a function which has its own special
this
configured, without using bind()
(for a single function call, and the avoidance of bind()
) - use
control
inside a function to remove that function from the chain because it only wants to run once pause()
the execution at one point to show making things asynchronous. also shows using both resume()
and resume.callback()
styles.- one function which does
control.fail()
when it can't do its work - one function which does
control.stop()
when it believes success has been achieved - one function does work both when it is first called, and, after the subsequent functions in the chain have executed
- provide a custom
context
to the run - show a "temporary override" of the context for the next function
- show a "permanent override" of the context
var buildChain = require('chain-builder')
, localFunctionsArray = require('./some/lib/fn/provider')
, chain = buildChain(localFunctionsArray)
chain.add(require('some-module-with-fns'))
chain.remove('the-one-we-dont-want')
chain.add(require('another-module'))
chain.enable('the.optin.id')
var selectCacheFns = chain.select(require('./select-cache-fns'))
selectCacheFns.remove('We are using a different caching method')
chain.disable('monitor')
var notBoundFn = someObject.someFunction
notBoundFn.options = { this: someObject }
function tempOverride(control) {
var contextForNextFunction = {}
control.context(contextForNextFunction)
}
function iRunOnce(control) {
control.remove()
}
function iPauseForAsync(control, context) {
var actions = control.pause('wait for file read')
, callback = actions.callback('read file failed', 'theFileContent')
fs.readFile('some/path/to/a/file.txt', 'utf8', callback)
fs.readFile('some/path/to/a/file.txt', 'utf8', function(err, content) {
if (err) {
actions.fail('read file failed', err)
} else {
context.theFileContent = content
actions.resume()
}
})
setTimeout(resume, 10*1000)
}
function iStopSometimes(control, context) {
if (this.theFileContent === 'we did it already') {
control.stop('we already have what we need')
} else {
control.context({}, true)
}
}
function iRetrySometimes(control) {
var result = control.next()
if (result.hasSomeRetryableProblem) {
result = control.next()
}
}
var context = {}
, runOptions = { context: context }
var runOptions2 = {
base: {
config: 'value',
helperFn: function(input) { return 'something' }
}
}
var result = chain.run(runOptions)
result = {
result : true,
context: ,
chain :
}
Usage: Considerations
chain.add()
- passing a single function, without an array, will be treated as an array with a single functionchain.add()
- passing multiple function arguments (not an array) will be treated as an array of those functions- the array can be changed which will affect any currently running chain executions (async) when they attempt to retrieve the next function to call
- be careful to ensure advanced context manipulations don't break others' functions
Usage: Ordering
Use ordering to order functions in the array. This allows contributing functions from multiple modules into a single chain and having them ordered based on advanced dependency constraints.
Here's an example of how to implement it with features:
- multiple changes won't trigger ordering multiple times because it is ordered once before a
run()
starts. - it will only order it when it has been changed since the last time it was ordered
order = require('ordering')
function markChanged(event) {
event.chain.__isOrdered = false
}
function ensureOrdered(event) {
if (event.chain.__isOrdered !== true) {
order(event.chain.array)
event.chain.__isOrdered = true
}
}
chain.on('add', markChanged)
chain.on('remove', markChanged)
chain.on('start', ensureOrdered)
Performance
Versions 0.13+ have a new benchark to test performance of parts of the API.
I used it to enhance the current performance considerably:
It also shows how important it is to provide a context which contains all the properties the chain run will use. Set them to null
as placeholders. This makes the context
object be in "fast mode". Otherwise, adding properties later will put it in "slomo". The benchmarks which do not define the properties ahead of time in the context all significantly longer to complete than the ones with them pre-defined.
Note, after that screenshot I updated the benchmark results formatting to show the nanoseconds for times so small they show as 0.000
seconds. I'll update this screenshot soon.
To run the benchmark:
npm run benchmark -- --repeat 1000000
Execution: Events
The chain emits these events:
- add - when functions are added to the chain
- remove - when functions are removed from the chain
- start - when a chain execution starts
- pause - when
control.pause()
is called - resume - when the
resume
function, returned from control.pause()
, is called - stop - when
control.stop()
is called - fail - when
control.fail()
is called - disable - when
control.disable()
or chain.disable()
is called - enable - when
chain.enable()
is called and its target actually needed enabling - done - when the chain is done executing because all functions have been run, or, because stop/fail were called
Execution: Control
By default, each function will be called in sequence and the this
will be the context
object. This supports a basic 'chain of command' with functions having no params using the this
to access the context.
Each function is called with the parameters (control, context)
. The control object is specific to each call to chain.run()
. The context object is either an empty object by default, or, the context specified to the chain.run()
call.
The control
parameter replaces the usual next
and allows changing the execution flow in multiple ways.
- pause - pauses execution until the returned resume callback is called
- stop - ends execution as a success
- fail - ends execution as an error
- context - override context for next function, or all functions, and continue chain execution
- next - continues the chain execution and then returns so you can do something after that and before you return a result
Also, a done
callback may be provided to the chain.run()
call. It will be called with (error, results)
when the chain is completed via any of the various flows.
Execution: Flow Styles
A "chain of command" example is in the simple example. Each function is called in sequence.
A "pipeline/filter" may call result = control.next()
to execute the later functions, then, do more work after that, and finally return the received result
(or their own result, if desired). This allows it to do work after the other functions were called. Note, this assumes a synchronous execution through the entire chain afterwards. If a later function uses control.pause()
then a result containing the paused info will be returned to control.next()
.
Asynchronous steps are achievable using actions = control.pause()
. Once called, the chain will wait to continue execution until the actions.resume()
function is called. The pause function accepts a reason
value which will stored in the control. Note, the current result will be returned back through the synchronous executions and return a final result containing the paused
information.
Sometimes it's helpful to end a chain early because its work is complete without calling the later functions. Use control.stop()
to do that. It will return a result back through the synchronous executions. The stop function accepts a reason
value which will be part of the returned results.
It's possible to end the chain because an error has occurred with control.fail()
. It will return a result back through the synchronous executions. The fail function accepts a reason
value which will be part of the returned results.
I've added an extra convenience to the resume
function provided by pause()
. It now has the ability to create a callback function with the standard params: (error, result)
. It can be provided to the usual asynchronous functions as the callback and it will handle calling control.fail()
if an error occurs or storing the result and calling resume()
. By default, the callback's error message is "ERROR" and the default key to store the result value into the context is result
. Override them by providing them as args to resume.callback(errorMessage, resultKey)
.
Execution: Use Context or This ?
You may choose to use either. It is up to your own preferred style. It is possible to customize the this via options on the function, so, in that instance the this will be different than the context so both can be used.
Execution: Stopping Chain
You may stop a chain's execution in two ways:
control.stop()
- means a successful conclusion was reachedcontrol.fail()
- means a failure prevents the chain from continuing
Each of these stores the same information into the control:
- reason - the reason value provided to the function call
- index - the index in the array of the function which called stop/fail
- fn - the function which called stop/fail
Here's an example of using control.stop()
:
var buildChain = require('chain-builder')
function fn1() { this.done = true }
function fn2(control) { if(this.done) control.stop('we are done') }
function fn3() { console.log('I wont run') }
var chain = buildChain({array:[ fn1, fn2, fn3 ]})
var result = chain.run({ context:{done:false} })
result = {
result : true,
stopped: { reason:'we are done', index:1, fn:fn2 },
chain :
}
Here's an example of using control.fail()
:
var buildChain = require('chain-builder')
function fn1() { this.problem = true }
function fn2(control) { if(this.problem) control.fail('there is a problem') }
function fn3() { console.log('I wont run') }
var chain = buildChain({ array:[ fn1, fn2, fn3 ] })
var context = { problem:false }
var result = chain.run({ context:context })
result = {
result: false,
failed: { reason:'there is a problem', index:1, fn:fn2 },
chain :
}
Example: Use Context to Pass on a Value
Shown many times above already, the context object is available to each function. Here is an explicit example of doing so:
var buildChain = require('chain-builder')
function fn1() { this.give = 'high 5' }
function fn2() { if(this.give == 'high 5') 'cheer' }
var chain = buildChain({ array:[ fn1, fn2 ] })
var result = chain.run()
Thrown Error
What if a function throws an error?
buildChain = require('chain-builder')
function fn1() { throw new Error('my bad') }
var chain = buildChain({ array:[fn1] })
var result = chain.run()
result = {
result: true,
context: {},
failed: {
reason: 'caught error',
index: 0,
fn : fn1,
error:
}
}
Example: Pipeline/Filter Style
This style allows performing work after the later functions return. It relies on synchronous execution.
Here is an example:
var buildChain = require('chain-builder')
function fn1(control) {
this.value = doSomeOperation()
var result = control.next()
if (result && !!result.error) {
this.anotherValue = someOtherOperation(this.valueFromLaterFunctions)
}
return result
}
function fn2() { this.valueFromLaterFunctions = someOperationOfItsOwn() }
var chain = buildChain({ array:[fn1, fn2] })
, result = chain.run()
result = {
value: 'something'
valueFromLaterFunctions: 'something more'
anotherValue: 'something else'
}
Example: Asynchronous
Although the above examples show synchronous execution it is possible to run a chain asynchronously using the control.pause()
function.
An example:
var buildChain = require('chain-builder')
function fn1() { console.log(this.message1) }
function fn2(control, context) {
console.log(this.message2)
var actions = control.pause('just because')
setTimeout(function() { actions.resume() }, 1000)
return
}
function fn3() { console.log(this.message3) }
var chain = buildChain({ array:[fn1, fn2, fn3] })
, result = chain.run({
context: {
message1: 'in fn1',
message2: 'in fn2 and pausing',
message3: 'resumed and in fn3'
}
})
result = {
paused: { reason: 'just because', index:1, fn:fn2 }
}
console.log('back from chain.run()')
If your code calls the resume
function then it will receive the final result usually returned by chain.run()
.
Using setTimeout()
and other similar functions will make extra work to get that. So, you may specify a done
callback to chain.run()
to receive the final results, or the error, when a chain run completes.
Here's an example:
var buildChain = require('chain-builder')
function fn1() { console.log(this.message1) }
function fn2(control, context) {
console.log(this.message2)
var actions = control.pause('just because')
setTimeout(function() { actions.resume() }, 1000)
return
}
function fn3() { console.log(this.message3) }
var chain = buildChain({ array:[fn1, fn2, fn3] })
var context = {
message1: 'in fn1',
message2: 'in fn2 and pausing',
message3: 'resumed and in fn3'
}
var result = chain.run({
context:context,
done: function(error, results) {
console.log('in done')
}
})
result = {
paused: { reason: 'just because', index:1, fn:fn2 }
}
console.log('back from chain.run()')
Advanced Contexts
There are two "contexts" which have been the same in all the above examples.
It is possible to make them different for advanced use. Please, be careful :)
Contexts: The two contexts
- shared context : the second argument passed to each function
function (control, context) {
console.log('<-- that context')
}
- this context : the this while each function is executing
function(control, context) {
console.log('that context-->', this.someProperty)
}
Contexts: How to specify the this
context
Provide an options object on the function which includes a this
property. When you do, the this
will be set as the this for the function.
var buildChain = require('chain-builder')
, specificThis = { some:'special this' }
function fn(control, sharedContext) {
console.log('*this* is specificThis. some=', this.some)
console.log('sharedContext is a shared ', sharedContext.shared)
}
fn.options = { this: specificThis }
var chain = buildChain({ array:[fn] })
chain.run({ context: shared:'object' })
Contexts: Why not use bind()
?
When using bind()
it wraps the function with another function which calls it with the this
context specified. That means to call the function it's now two function calls.
When specifying the this
via an option it is used in the fn.call()
as an argument. It doesn't make more work.
See:
function fn1Original() { console.log(this.message) }
var fn1This = { message:'I am two calls' }
, fn1Bound = fn1.bind(fn1This)
function fn2() { console.log(this.message) }
var fn2This = { message:'I am one call' }
fn2.options = { this:specialThis }
var chain = buildChain()
chain.add(fn1Bound, fn2)
chain.run()
fn1Bound.call(context, control, context)
fn1Bound() {
fn1Original.apply fn1This, Array.prototype.slice.call arguments
}
fn2.call(fn2.options.this, control, context)
Note, using control.context()
overrides the context provided to the next function. Using fn.options.this
overrides the this. This means, it's possible to completely change what a function receives as context and this and have a different view than all other functions called in a chain.
Contexts: How to Control the Shared Context
The shared context can be manipulated in multiple ways.
When calling chain.run()
you may specify a context as an option: chain.run context:{}
. See more in the chain.run() API.
Once a chain is running you may alter the context used both permanently and impermanently using the chain.context()
function. That function will also execute the next function in the chain.
control.context newContext
- this will override the context used by the next function called only. the default context is unaffected and will be used by functions after the next one. A function may choose to pass on this impermanently overridden context by doing an override (control.context context
) again and passing the context to it.control.context newContext, true
- the true means it's a permanent override. It will set the default context used for all subsequent functions. Also, the default context is supplied in the final results, so, a permanently overridden context will then be in the final results.
Here is an example of an impermanent override:
var buildChain = require('chain-builder')
, defaultContext = {}
, overrideContext = {}
function fn1() { this.fn1 = 'this' }
function fn2(control, context) { context.fn2 = 'context' }
function fn3(control) {
this.fn3 = 'this'
control.context(overrideContext)
}
function fn4(control, context) {
this.fn4 = 'this'
context.fn4 += ', context'
}
function fn5(control, context) {
this.fn5 = 'this'
context.fn5 += ', context'
}
var chain = buildChain({ array:[fn1, fn2, fn3, fn4, fn5] })
, result = chain.run({ context:defaultContext })
result = {
result: true,
context: {
fn1: 'this',
fn2: 'context',
fn3: 'this',
fn5: 'this, context'
},
chain:
}
overrideContext = {
fn4: 'this, context'
}
Only fn4
received the overrideContext
.
Here is an example of a permanent override:
var buildChain = require('chain-builder')
, defaultContext = {}
, overrideContext = {}
function fn1() { this.fn1 = 'this' }
function fn2(_, context) { context.fn2 = 'context' }
function fn3(control) {
this.fn3 = 'this'
control.context(overrideContext, true)
}
function fn4(control, context) {
this.fn4 = 'this'
context.fn4 += ', context'
}
function fn5(control, context) {
this.fn5 = 'this'
context.fn5 += ', context'
}
var chain = buildChain({ array:[fn1, fn2, fn3, fn4, fn5] })
, result = chain.run({ context:defaultContext })
result = {
result: true,
context: {
fn4 : 'this, context',
fn5 : 'this, context'
}
}
defaultContext = {
fn1: 'this',
fn2: 'context',
fn3: 'this'
}
overrideContext = {
fn4: 'this, context',
fn5: 'this, context'
}
API
API: Exported Builder Function
The module exports a single "builder" function. It accepts one parameter which can be various types. It returns a new Chain
instance.
Parameter can be:
- function - a single function to put into the chain
- array - an array containing functions to put into the chain. The array contents are validated immediately and an object with an 'error' property is returned if the array contains anything other than functions.
- object - an object containing any of the below "options"
Options:
- array - the "array" described above as a parameter.
- base - used in the default context builder as the first param to
Object.create()
- props - used in the default context builder as the second param to
Object.create()
- buildContext - overrides the context builder. It is described below with an example.
Use the base or props options
The base
and props
are used in Object.create(base, props)
to build the default context object. You may provide functions, or anything you could specify in an object's prototype
. This allows adding helper functions and constants to the context
object.
Example:
function worker(control, context) {
context.num = context.sum(context.num, 456)
}
var base = {
num: 123,
sum: function(a, b) { return a + b }
}
, runOptions = { base: base }
, result = chain.run(runOptions)
result = {
result : true,
chain : ,
context: {
num: 579
}
}
Override the Context Builder Function
The buildContext
option, the "context builder", is a function accepting the "options" object provided to chain.run()
and returning the object to use as the context
for that execution run. If the options
object contains a context
property then it should return that object, but, it may alter it before doing so.
Example:
var buildChain = require('chain-builder')
, SomeClass = require('some-module')
function contextBuilder(options) {
if (options && options.context) {
return options.context
} else {
return new SomeClass((options && options.someOptions) || {})
}
}
function worker(control, context) {
var something = context.useSomeFunction()
context.doSomethingElse()
}
var chain = buildChain({ buildContext: contextBuilder, array:[ worker ] })
, runOptions = { someOptions:{ example: 'value' } }
, result = chain.run(runOptions)
result = {
result : true,
chain : ,
context: { }
}
API: Chain
API: chain.run()
Primary function which performs a single execution of all functions in the chain.
Parameters:
- options - object containing the below options
- done - an optional callback function added as a listener to the 'done' event.
Options Parameter:
- done - the 'done' callback function added as a listener to the 'done' event
- context - the context object provided to each function
- base - when the
context
option isn't specified then chain
will build a context object using chain._buildContext()
. That uses Object.create(base, props)
to build the object. The base
option will be used there. When not specified then an empty object is used. - props - As described in for
base
, when the 'context' option isn't specified the default context is built with this option as the second arg to Object.create(base, props)
. When not specified then undefined
is passed.
Returns an object with properties:
-
result - true/false depending on success
-
context - the final context object. It may be the default one created, the one specified in the first parameter, or one provided by a function as a "permanent override".
-
chain - the chain. May seem weird to provide it, but, the same "result" is provided to the "done" callback and that may be added to more than one chain.
-
paused / stopped / failed - when a function calls pause(), stop(), or fail() then their corresponding property contains:
-
reason - the reason
provided to the call, or, true
-
index - the index of the function in the chain which called it
-
fn - the function in the chain which called it
-
failed - as described above, and, it may have an additional property error with an Error
instance caught during execution.
-
removed - an array of functions removed via control.remove()
during the execution
Keep in mind, the returned object may not have the final contents when control.pause()
is used because it receives back what's available up to the pause point.
Examples:
var result
result = chain.run()
result = chain.run({ context:{} })
result = chain.run({ context:{}}, function onDone() {})
result = chain.run({ context:{}, done: function onDone() {
}})
result = chain.run({ base: someProtoObject })
result = chain.run({ base: someProtoObject, props: somePropsDesc })
result = chain.run({ buildContext: function contextBuilder(options) {
}})
API: chain.add()
Add functions to the end of the chain's array.
Parameters:
- all parameters may be a function and they will be grouped in an array and used in the chain
- parameter may be an array. each element will be checked to ensure they are all functions otherwise an object is returned with an
error
property. they are added to the end of the chain's array.
Returns:
An object is returned with a result
property which has a true
value for success, and an added
property containing an array of all functions added.
Event:
An add
event is emitted with the same object described in the "Returns" section above.
Examples:
chain.add(fn1)
chain.add(fn1, fn2, fn3)
chain.add([ fn1, fn2, fn3 ])
API: chain.remove()
Remove functions from the chain's array.
Parameters:
- the first parameter may have three different types:
- A number is used as an index into the array specifying which function to remove. If the index is invalid then an object is returned with an
error
property saying the index is invalid. - A function is used as the function to remove. The array is searched for the function. If it is found it is removed.
- A string is used as the
id
of the function to remove. A function's id
is in its options
object. If a function with the id is found then it is removed.
- the second parameter is optional and may be anything you want. It is the "reason" for doing the removal. The "reason" is included in the return results and in the object provided to the 'remove' event.
Returns:
An object with properties:
- result - true for success, false when it couldn't be found to remove
- reason - supplied reason, or true by default, or 'not found' when
result
is false. - removed - an array containing the functions which were removed
Event:
A remove
event is emitted with the same object described above as the return object.
Examples:
var result
result = chain.remove(2)
result = chain.remove(fn1)
result = chain.remove('theid')
result = chain.remove(, 'some reason')
result = {
result : true,
reason : true,
removed: [ ]
}
result = {
result: false,
reason: 'Invalid index: 0'
}
result = {
result: false,
reason: 'not found'
}
result = {
result: false,
reason: 'Requires a string (ID), an index, or the function',
which :
}
API: chain.clear()
Removes all functions from the chain and emits a clear
event containing the removed functions.
If the chain is already empty then the return, and the emitted event, will have a false result
and reason
'chain is empty'.
Example:
var result = chain.clear()
result = {
result: true,
removed: [ ],
reason: 'chain empty'
}
API: chain.disable()
Disable the entire chain or a specific function in the chain.
A disabled chain will not run()
. If run()
is called it will return an object with result=false
, reason
will be 'chain disabled', and disabled
will be the reason it was disabled.
A disabled function will be skipped during an execution run.
Parameters for disabling the entire chain:
- this optional param is the reason for disabling the chain.
- there's no second param when disabling the entire chain.
Parameters for disabling a single function:
- a required value used to specify which function to disable. Read about these three types above in chain.remove().
- the second param is normally optional, but, because
disable()
may also apply to the chain itself, we must differentiate chain.disable(reasonString)
from chain.disable(functionIdString)
. So, if the first param is a number or a function then this second param is options. If the first param is a string representing the id
of a function, then, you must provide this second arg. If you don't care about its value, simply specify true
.
Returns:
An object with properties:
- result - true for success, false when the function couldn't be found to disable
- reason - supplied reason, or true by default, or 'not found' when
result
is false. - chain - if the chain is disabled then it is included in the result
- fn - if a funciton is removed then it is included in the result
Event:
A disable
event is emitted containing the same result object as the return object described above.
Examples:
var result
result = chain.disable()
result = {
result: true,
reason: true,
chain:
}
result = chain.disable('some reason')
result = {
result: true,
reason: 'some reason',
chain:
}
var which
which = 3
which = someFunction
which = 'theid'
result = chain.disable(which, 'some reason')
result = {
result: true,
reason: 'some reason',
fn :
}
chain.disable(3)
chain.disable(someFn)
chain.disable('some-id')
API: chain.enable()
Enable the entire chain or a specific function in the chain.
Parameters:
- the only parameter is determines whether to enable the chain or a function, and, which function to enable. No first parameter means enable the whole chain. Otherwise, the first param can have three types. Read about these three types above in chain.remove().
Returns:
An object with properties:
- result - true for success, false when the function couldn't be found to disable
- reason - supplied reason, or true by default, or 'not found' when
result
is false. - chain - if the chain is disabled then it is included in the result
- fn - if a function is removed then it is included in the result
Note, if the target is not disabled then the return result will be false
and contain the reason
'chain not disabled' or 'function not disabled'.
Event:
A enable
event is emitted containing the same result object as the return object described above.
Note, if the target is not disabled then no enable
event will be emitted.
Examples:
var result
result = chain.enable()
result = {
result: true,
chain:
}
var which
which = 3
which = someFunction
which = 'theid'
result = chain.enable(which)
result = {
result: true,
fn :
}
result = {
result: false,
reason: 'chain wasn\'t disabled',
reason: 'function wasn\'t disabled',
}
API: chain.select()
Provide a function to select functions in the chain to apply a sub-operation to.
Parameters:
- The first parameter is the only one. It must be a function which returns true for a function to include, and false for exclude.
Returns:
An object with four sub-operation functions available:
- remove - same as the
chain.remove()
function described above except called with each the selected function as the first parameter and the sub-operation's parameters provided as the second, and later, parameters. The first parameter of this remove()
is the reason
for the removal. It is optional. - disable - same as described for
remove
except for the chain.disable()
function. - enable - same as described for
remove
except for the chain.enable()
function. - affect - a special sub-operation which doesn't provide the sub-operation's action function. You provide that as the first parameter of this
affect()
call.
The function provided to select()
receives two parameters:
- the function it must choose to include or exclude
- the index in the chain's array where the function is
Examples:
function selector(fn, index) {
return ()
}
var select = chain.select(selector)
select.remove('some reason')
select.disable('any reason')
select.enable('blah reason')
select.affect(function(fn, index) {
})
API: Control
Each chain.run()
execution creates a new Control
instance to oversee it and provide functionality to the functions being executed.
The control
instance is provided to each executed function as the first parameter.
It's not required to make use of the control
. Each function can ignore it and the sequential execution of the chain's functions will happen.
Example:
function (control) {
}
API: control.next()
Use next()
only if you want to do work both before and after the later functions have run through. This is the "pipeline" or "filter" pattern because it allows a function to alter what's provided to the later functions and then do something with the results after they've run. That wouldn't be possible if it was only first or only last.
Parameters:
- context - optionally specify a new context object for the next function(s)
- permanent - specify whether the override context is only for the next function, or, if it's permanent. If
true
then it will replace all future contexts and become part of the final result returned back to next()
. If left out, or, false
, then it will only be given to the next function called. Note, that function may then choose to pass on the context.
Returns:
A final results object like what chain.run() receives.
Examples:
function worker(control) {
this.something = 'new value'
var result = control.next()
}
function overridingWorker(control) {
var newContext = { override: 'context' }
var result = control.next(newContext, true)
}
function retryWorker(control) {
var result = control.next()
if (result.failed && result.failed.reason == 'some retry-able reason') {
result = control.next()
}
}
API: control.context()
Temporarily change the context given to the next function, or, permanently change the context for all subsequent functions and make it part of the final results.
Parameters:
- context - optionally specify a new context object for the next function(s)
- permanent - specify whether the override context is only for the next function, or, if it's permanent. If
true
then it will replace all future contexts and become part of the final result
. If left out, or, false
, then it will only be given to the next function called. Note, that function may then choose to pass on the context.
Returns:
Returns undefined.
Examples:
function tempOverrider(control) {
var tempContext = { temp: 'context' }
control.context(tempContext)
}
function overrider(control) {
var newContext = { replacement: 'context' }
control.context(newContext)
}
API: control.pause()
Asynchronous execution is possible using control.pause()
to retrieve an object with a resume()
function. The chain will wait until that function is called to begin executing again.
There is an additional helper function on the returned object named callback
. Use that to create a resume callback which accepts the standard parameters (error, result)
and handles calling control.fail()
with an error message and the error, or, setting the result
into the context for you.
Parameters:
- reason - optionally specify a reason for pausing so it's available.
Returns:
An object 3 actions and a callback generator:
- resume - used to begin chain execution from where it was paused
- stop - ends the pause and stops execution at the point where it was paused. Accepts a reason argument.
- fail - same as stop except it's a failure and accepts both a reason and an error argument like
control.fail()
. - callback - function to create an
(error, result)
style callback function which handles those for you.
Callback helper Parameters:
- optional error message which will be passed to
control.fail()
if an error occurs. Defaults to "ERROR". - optional property name (result key) to set the result into the
context
with. Defaults to "result".
Event:
Emits a 'pause' event with the paused
object as described below.
Examples:
A simple use of the resume function:
function simpleResume(control) {
var actions = control.pause('wait for a bit')
setTimeout(function() { actions.resume() }, 1234)
}
Using resume()
within a callback:
function worker(control, context) {
var actions = control.pause('because i said so')
fs.readFile('./some/file.ext', 'utf8', function callback(error, content) {
if (error) {
actions.fail('Failed to read config file', error)
} else {
context.fileContent = content
actions.resume()
}
})
}
Using the resume.callback()
for the same results:
function worker(control) {
var actions = control.pause('because i said so')
, callback = actions.callback('Failed to read config file', 'fileContent')
fs.readFile('./some/file.ext', 'utf8', callback)
}
function onDone(error, result) {
if (error) {
error = {
reason: 'Failed to read config file',
index : 0,
fun : worker
}
result = {
result : false,
chain: ,
context: {},
failed: { }
}
} else {
result = {
result : true,
chain : ,
context: {}
}
}
}
var result = chain.run({}, onDone)
result = {
paused: {
reason: 'because i said so',
index : 0,
fn : worker
}
}
API: control.stop()
Stops executing the chain and returns a success result with the reason provided to stop()
.
Note, this is for early termination of an execution. Not for an error. When there's an error use control.fail().
Parameters:
- reason - optionally specify a reason for stopping so it's in the results.
Returns:
true
. Yup, that's it. :)
Event:
Emits a 'stop' event with an object containing both the current context
and the stopped
object as described below.
Example:
function stopper(control) {
if (this.somethingMeaningWeAreDone) {
return control.stop('we have what we need')
}
}
result = {
result : true,
chain : ,
stopped: {
reason: 'we have what we need',
index: 0,
fn : stopper
}
}
API: control.fail()
Stops executing the chain and returns a failure result with the reason provided to fail()
.
Note, this is for an error during execution. If you want to simply stop execution then use control.stop().
Parameters:
- reason - optionally specify a reason for failing so it's in the results.
Returns:
false
. Yup, that's it. :)
Event:
Emits a 'fail' event with an object containing both the current context
and the failed
object as described below.
Example:
function failer(control) {
if (this.somethingBad) {
return control.fail('the sky is falling!')
}
}
result = {
result: false,
chain: ,
failed: {
reason: 'the sky is falling!',
index: 0,
fn : failer
}
}
API: control.disable()
Disables the currently executing function. It will be skipped during execution runs until it is enabled.
This allows functions to disable themselves without resorting to calling chain.disable()
with its necessary params.
Parameters:
- reason - optionally specify a reason for disabling. Defaults to
true
.
Returns:
The same object as described above in chain.disable().
Event:
Emits a 'disable' event just like chain.disable().
Examples:
function disabler(control) {
return control.disable('I\'ve had enough for now.')
}
API: control.remove()
Removes the currently executing function.
This allows functions to remove themselves without resorting to calling chain.remove()
with its necessary params.
When a function is removed this way its removal is recorded by the Control and it will be in the final results.
Parameters:
- reason - optionally specify a reason for removing the function. Defaults to
true
.
Returns:
true
, Yup, that's it.
Event:
Emits a 'remove' event just like chain.remove().
Examples:
function quitter(control) {
return control.remove('I quit.')
}
result = {
result: true,
chain : ,
context: {},
removed: [
quitter
]
}