Skip to content

Commit

Permalink
simplify process loop with one loop var and one result var, change fa…
Browse files Browse the repository at this point in the history
…iled to error
  • Loading branch information
elidoran committed May 19, 2017
1 parent 95ced71 commit 2c289af
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 73 deletions.
63 changes: 22 additions & 41 deletions lib/Control.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,26 @@ module.exports = class Control

@_node = null
@_after = null
@_failed = null
@waiting = null
@_result = null
@_error = null
@_loop = true


_reset: ->
@_failed = null
@_node = null
@_after = null
@waiting = null
@_result = null
@_error = null
@_beforesAdded = []
@_next = [ @_start ]
@_context.$clear?()


fail: (error, details) ->

@_loop = false # stop processing

if typeof error is 'string' then error = new StatingError error, details

# copy details properties onto Error
Expand All @@ -49,15 +51,17 @@ module.exports = class Control
error.node = @_node
error.context = context

@_failed = error
@_error = error
return

wait: (info) ->
@waiting = info ? 'waiting'
@_loop = false # stop processing
@_result = info ? 'waiting'
return

# must use separate result var because result can be `null`
result: (value) ->
@_loop = false # stop processing
@_result = value ? NIL
return

Expand Down Expand Up @@ -94,41 +98,18 @@ module.exports = class Control

@_context.$add data

# TODO:
# set a single result object for result/wait/fail,
# then, test for the one result object and return it.
# returning a null value as the result makes this tough.

loop # the main processing loop. checks if it has input then calls next

# 1. if we should wait for input, remove the marker and then return/done
if @waiting?
result = @waiting
@waiting = null
return done null, result

# 2. if we want to return a result, remove the marker and then return/done
# NOTE:
# this supports repeated sycnchronous calls to retrieve one value at
# a time. the main goal of `stating` is processing of chunks which
# can provide many values via a different outlet than the sycnchronous
# return value. but, it's useful for dev/testing/perf to do this too.
if @_result?
result = if @_result is NIL then null else @_result
@_result = null
return done null, result

# 3. if we @_failed then tell done() and return
if @_failed?
result = @_failed
@_failed = null
# reset everything so we can start over.
@_reset()
# the `_failed` has the context in it for them.
return done result

# 4. get the next node and call it
@_call @_prepareNext done
@_loop = true
@_error = @_result = null # ensure we don't have stored result/error

# the main processing loop. gets next node and calls it.
# NOTE: isolates the try-catch in its own function because only the newest
# node versions can optimize a function with a try-catch.
@_call @_nextNode() while @_loop # reset it to true at start

# if they provided the callback then we're giving them the info as usual.
# if they didn't, then it's the placeholder which returns either
# the error, if it exists, or the result.
done @_error, if @_result is NIL then null else @_result


_call: (node) ->
Expand All @@ -140,7 +121,7 @@ module.exports = class Control
@fail error


_prepareNext: ->
_nextNode: ->

next = @_next

Expand Down
64 changes: 32 additions & 32 deletions test/lib/test-control.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ describe 'test control', ->

control._reset()

assert.equal control._failed, null
assert.equal control._error, null
assert.equal control._node, null
assert.equal control._after, null
assert.equal control.waiting, null
assert.equal control._loop, true
assert.equal control._result, null
assert.equal control._error, null
assert.deepEqual control._beforesAdded, []
assert.deepEqual control._next, [noop]

Expand All @@ -28,10 +29,10 @@ describe 'test control', ->
error = new Error 'cuz'
control.fail error

assert.equal control._failed.message, 'cuz'
assert.equal control._failed.node, 'testing2'
assert.notStrictEqual control._failed.context, context
assert.deepEqual control._failed.context, context
assert.equal control._error.message, 'cuz'
assert.equal control._error.node, 'testing2'
assert.notStrictEqual control._error.context, context
assert.deepEqual control._error.context, context


it 'should add details to failure error', ->
Expand All @@ -42,23 +43,23 @@ describe 'test control', ->
error = new Error 'cuz'
control.fail error, extra: 'details'

assert.equal control._failed.message, 'cuz'
assert.equal control._failed.node, 'testing2'
assert.notStrictEqual control._failed.context, context
assert.deepEqual control._failed.context, context
assert.equal control._failed.extra, 'details'
assert.equal control._error.message, 'cuz'
assert.equal control._error.node, 'testing2'
assert.notStrictEqual control._error.context, context
assert.deepEqual control._error.context, context
assert.equal control._error.extra, 'details'

it 'should use string to create failure error', ->

control = new Control {nodes:{}, _start:'noop'}, context: {}
control.fail 'message'
assert.equal control._failed.message, 'message'
assert.equal control._error.message, 'message'

it 'should accept a failure error w/out details', ->

control = new Control {nodes:{}, _start:'noop'}, context: {}
control.fail new Error 'blah'
assert.equal control._failed.message, 'blah'
assert.equal control._error.message, 'blah'


it 'should use context $copy', ->
Expand All @@ -69,50 +70,49 @@ describe 'test control', ->
error = new Error 'cuz'
control.fail error, extra: 'testing'

assert.equal control._failed.message, 'cuz'
assert.equal control._failed.node, 'testing2'
assert.notStrictEqual control._failed.context, context
assert.deepEqual control._failed.context, some:'copy'
assert.equal control._failed.extra, 'testing'
assert.equal control._error.message, 'cuz'
assert.equal control._error.node, 'testing2'
assert.notStrictEqual control._error.context, context
assert.deepEqual control._error.context, some:'copy'
assert.equal control._error.extra, 'testing'


it 'should function as done instead of data', ->
it 'should use function as done instead of data', ->

calledData = {}
add = (data) -> calledData = data
control = new Control {nodes:{}, _start:'noop'}, context: $add: add
control.fail 'testing'
failed = control._failed
noop = (control) -> control.result 'testing'
control = new Control {nodes:{noop}, _start:'noop'}, context: $add: add
calledError = calledResult = null
done = (error) -> calledError = error
done = (error, result) -> calledError = error ; calledResult = result
control._begin done

assert.equal calledData, null
assert.deepEqual calledError, failed
assert.equal calledError, null
assert.deepEqual calledResult, 'testing'


it 'should be capable of returning a null result', ->

control = new Control {nodes:{}, _start:'noop'}, context: $add: ->
control.result null
assert.deepEqual control._result, {} # NIL is an empty object
noop = (control) -> control.result null
control = new Control {nodes:{noop}, _start:'noop'}, context: $add: ->
result = control._process()
assert.strictEqual result, null

it 'should error in _call() w/out node', ->

control = new Control {nodes:{}, _start:'noop'}, context: {}
control._call()
assert.equal control._failed?.message, 'no next node to call'
assert.equal control._error?.message, 'no next node to call'

it 'should catch error in _call() when node throws', ->

control = new Control {nodes:{}, _start:'noop'}, context: {}
control._call -> throw 'error'
assert.equal control._failed?.message, 'error'
assert.equal control._error?.message, 'error'

it 'should error in _prepareNext() w/out next node', ->
it 'should error in _nextNode() w/out next node', ->

control = new Control {nodes:{}, _start:'noop'}, context: {}
control._prepareNext()
assert control._failed?.message, 'no next node'
control._nextNode()
assert control._error?.message, 'no next node'

0 comments on commit 2c289af

Please sign in to comment.