Repository: ViacomInc/data-point
Pull request #12 (2017-10-22 18:00:19)

Reducer function async
Closes https://github.com/ViacomInc/data-point/issues/10

coveralls: 
[![Coverage Status](https://coveralls.io/builds/13831818/badge)](https://coveralls.io/builds/13831818)

Coverage remained the same at 100.0% when pulling **c65222892b6a3e67c6d2713a6875d26e9578f133 on acatl:reducer-function-async** into **0f85ddb3704dc8df144616208217c00417a6b897 on ViacomInc:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/13831814/badge)](https://coveralls.io/builds/13831814)

Coverage remained the same at 100.0% when pulling **c65222892b6a3e67c6d2713a6875d26e9578f133 on acatl:reducer-function-async** into **0f85ddb3704dc8df144616208217c00417a6b897 on ViacomInc:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/13846106/badge)](https://coveralls.io/builds/13846106)

Coverage remained the same at 100.0% when pulling **dcb2a5b8bb0145211c54348bc8bb49a8b62f915f on acatl:reducer-function-async** into **3e80c6dc4447733fbacbbba560d3a41f7a47fa40 on ViacomInc:master**.


Patch:
diff --git a/README.md b/README.md
index 1dc2cf2..a9fa368 100644
--- a/README.md
+++ b/README.md
@@ -65,8 +65,8 @@ const dataPoint = DataPoint.create()
 
 // reducer function that appends 'World' to the
 // value of the accumulator
-const reducer = (acc, next) => {
-  next(null, acc.value + ' World')
+const reducer = (acc) => {
+  return acc.value + ' World'
 }
 
 // applies reducer to input
@@ -268,7 +268,7 @@ dataPoint.transform(t, someInput).then((acc) => {
 **Single Function Reducer:**
 
 ```js
-const t = (acc, next) => { ... }
+const t = (acc) => { ... }
 ```
 
 **Array transform with mixed reducers:**
@@ -277,9 +277,9 @@ const t = (acc, next) => { ... }
 // just strings
 const t = ['$image[0]', 'model:Image']
 // string & Function reducers
-const t = ['$image[0].url', (acc, next) => { ... }]
+const t = ['$image[0].url', (acc) => { ... }]
 // piped strings & functions
-const t = ['$results[0].users | model:Users', (acc, next) => { ... }]
+const t = ['$results[0].users | model:Users', (acc) => { ... }]
 
 // which could be later used as:
 dataPoint.transform(t, someInput).then((acc) => {
@@ -405,7 +405,86 @@ Example at: [examples/reducer-path.js](examples/reducer-path.js)
 
 ### <a name="function-reducer">FunctionReducer</a>
 
-A FunctionReducer allows you to execute an asynchronous block of code. Its first parameter is an [Accumulator](#accumulator) object. The second is an error-first callback ([Node.js style callback](https://nodejs.org/api/errors.html#errors_node_js_style_callbacks)) that has the arguments `(error, value)`, where value will be the _value_ passed to the _next_ transform; that value will eventually be the value of the fully resolved transformation.
+A FunctionReducer allows you to use a function to apply a transformation. There are a couple of ways you may write your FunctionReducer:
+
+- Synchronous `function` that returns new value
+- Asynchronous `function` that returns a `Promise`
+- Asynchronous `function` with callback parameter
+- Asynchronous through [async/await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) `function` **only if your environment supports it**
+
+**IMPORTANT:** Be careful with the parameters passed to your reducer function, DataPoint relies on the number of arguments to detect the type of ReducerFunction it should expect. 
+
+#### <a name="function-reducer-sync">Returning a value (synchronous)</a>
+
+The returned value is used as the new value of the transformation.
+
+**SYNOPSIS**
+
+```js
+const name = (acc:Accumulator) => {
+  return newValue
+}
+```
+
+**Reducer's arguments**
+
+| Argument | Type | Description |
+|:---|:---|:---|
+| *acc* | [Accumulator](#accumulator) |  Current reducer's accumulator Object. The main property is `acc.value`, which is the current reducer's value. |
+
+**EXAMPLE:**
+
+```js
+const reducer = (acc) => {
+  return acc.value + ' World'
+}
+
+dataPoint
+  .transform(reducer, 'Hello')
+  .then((acc) => {
+    assert.equal(acc.value, 'Hello World')
+  })
+```
+
+Example at: [examples/reducer-function-sync.js](examples/reducer-function-sync.js)
+
+#### <a name="function-reducer-returns-a-promise">Returning a Promise</a>
+
+If you return a Promise its resolution will be used as the new value of the transformation. Use this pattern to resolve asynchronous logic inside your reducer.
+
+**SYNOPSIS**
+
+```js
+const name = (acc:Accumulator) => {
+  return Promise.resolve(newValue)
+}
+```
+
+**Reducer's arguments**
+
+| Argument | Type | Description |
+|:---|:---|:---|
+| *acc* | [Accumulator](#accumulator) |  Current reducer's accumulator Object. The main property is `acc.value`, which is the current reducer's value. |
+
+**EXAMPLE:**
+
+```js
+const reducer = (acc) => {
+  return Promise.resolve(acc.value + ' World')
+}
+
+dataPoint
+  .transform(reducer, 'Hello')
+  .then((acc) => {
+    assert.equal(acc.value, 'Hello World')
+  })
+```
+
+Example at: [examples/reducer-function-promise.js](examples/reducer-function-promise.js)
+
+#### <a name="function-reducer-with-callback">With a callback parameter</a>
+
+Accepting a second parameter as a callback allows you to execute an asynchronous block of code. This callback is an error-first callback ([Node.js style callback](https://nodejs.org/api/errors.html#errors_node_js_style_callbacks)) that has the arguments `(error, value)`, where value will be the _value_ passed to the _next_ transform; this value becomes the new value of the transformation.
 
 **SYNOPSIS**
 
@@ -419,7 +498,7 @@ const name = (acc:Accumulator, next:function) => {
 
 | Argument | Type | Description |
 |:---|:---|:---|
-| *acc* | [Accumulator](#accumulator) | Holds information regarding the current reducer's execution. The main property is `acc.value`, which is the current reducer's value. |
+| *acc* | [Accumulator](#accumulator) |  Current reducer's accumulator Object. The main property is `acc.value`, which is the current reducer's value. |
 | *next* | `Function(error,value)` | [Node.js style callback](https://nodejs.org/api/errors.html#errors_node_js_style_callbacks), where `value` is the value to be passed to the next reducer.
 
 **EXAMPLE:**
@@ -429,13 +508,14 @@ const reducer = (acc, next) => {
   next(null, acc.value + ' World')
 }
 
-dataPoint.transform(reducer, 'Hello').then((acc) => {
-  assert.equal(acc.value, 'Hello World')
-  console.log(acc.value)
-})
+dataPoint
+  .transform(reducer, 'Hello')
+  .then((acc) => {
+    assert.equal(acc.value, 'Hello World')
+  })
 ```
 
-Example at: [examples/reducer-function.js](examples/reducer-function.js)
+Example at: [examples/reducer-function-with-callback.js](examples/reducer-function-with-callback.js)
 
 Throw an error from the reducer:
 
@@ -459,6 +539,15 @@ Example at: [examples/reducer-function-error.js](examples/reducer-function-error
 Higher Order Reducers are expected to be [Higher-order Functions](https://en.wikipedia.org/wiki/Higher-order_function). This means that a higher order reducer **MUST** return a [FunctionReducer](#function-reducer).
 
 ```js
+// sync
+const name = (param1, param2, ...) => (acc:Accumulator) => {
+  return newValue
+}
+// async via promise
+const name = (param1, param2, ...) => (acc:Accumulator) => {
+  return Promise.resolve(newValue)
+}
+// async via callback
 const name = (param1, param2, ...) => (acc:Accumulator, next:function) => {
   next(error:Error, newValue:*)
 }
@@ -467,8 +556,8 @@ const name = (param1, param2, ...) => (acc:Accumulator, next:function) => {
 **EXAMPLE**
 
 ```js
-const addStr = (value) => (acc, next) => {
-  next(null, acc.value + value)
+const addStr = (value) => (acc) => {
+  return acc.value + value
 }
 
 dataPoint.transform(addStr(' World!!'), 'Hello').then((acc) => {
@@ -490,8 +579,8 @@ const input = {
   }
 }
 
-const toUpperCase = (acc, next) => {
-  next(null, acc.value.toUpperCase())
+const toUpperCase = (acc) => {
+  return acc.value.toUpperCase()
 }
 
 dataPoint.transform(['$a.b', toUpperCase], input).then((acc) => {
@@ -504,18 +593,17 @@ Example at: [examples/reducer-array-mixed.js](examples/reducer-array-mixed.js)
 The following example extracts the array from the input object, gets its max value, and multiplies that value by a given number.
 
 ```js
-const multiplyBy = (factor) => (acc, next) => {
-  next(null, acc.value * factor)
+const multiplyBy = (factor) => (acc) => {
+  return acc.value * factor
 }
 
-const getMax = () => (acc, next) => {
+const getMax = () => (acc) => {
   const result = Math.max.apply(null, acc.value)
-  next(null, result)
+  return result
 }
 
 dataPoint.transform(['$a.b.c', getMax(), multiplyBy(10)], input).then((acc) => {
   assert.equal(acc.value, 30)
-  console.log(acc.value)
 })
 ```
 
@@ -552,8 +640,8 @@ const input = {
   }
 }
 
-const toUpperCase = (acc, next) => {
-  next(null, acc.value.toUpper())
+const toUpperCase = (acc) => {
+  return acc.value.toUpper()
 }
 
 dataPoint.addEntities({
@@ -584,8 +672,8 @@ const input = {
   ]
 }
 
-const toUpperCase = (acc, next) => {
-  next(null, acc.value.toUpper())
+const toUpperCase = (acc) => {
+  return acc.value.toUpper()
 }
 
 dataPoint.addEntities({
@@ -605,7 +693,7 @@ Reducer entity [Examples](test/definitions/integrations.js)
 **Best Practices (Recommendations) with reducers**
 
 ```js
-const badReducer = () => (acc, done) => {
+const badReducer = () => (acc) => {
   // this is BAD - dont be this person! 
   // never ever modify the value object.
   acc.value[1].username = 'foo'
@@ -616,18 +704,18 @@ const badReducer = () => (acc, done) => {
   image.username = 'foo'
 
   // pass value to next reducer
-  done(null, acc.value)
+  return acc.value
 }
 
 // this is better
 const fp = require('lodash/fp')
-const goodReducer = () => (acc, done) => {
+const goodReducer = () => (acc) => {
   // https://github.com/lodash/lodash/wiki/FP-Guide
   // this will cause no side effects
   const value = fp.set('[1].username', 'foo', acc.value)
 
   // pass value to next reducer
-  done(null, value)
+  return value
 }
 ```
 
@@ -731,14 +819,14 @@ const input = {
   }
 }
 
-const getMax = (acc, next) => {
+const getMax = (acc) => {
   const result = Math.max.apply(null, acc.value)
-  next(null, result)
+  return result
 }
 
-const multiplyBy = (number) => (acc, next) => {
+const multiplyBy = (number) => (acc) => {
   const newValue = acc.value * number
-  next(null, newValue)
+  return newValue
 }
 
 dataPoint.addEntities({
@@ -794,14 +882,14 @@ const input = {
   }
 }
 
-const getMax = (acc, next) => {
+const getMax = (acc) => {
   const result = Math.max.apply(null, acc.value)
-  next(null, result)
+  return result
 }
 
-const multiplyBy = (number) => (acc, next) => {
+const multiplyBy = (number) => (acc) => {
   const newValue = acc.value * number
-  next(null, newValue)
+  return newValue
 }
 
 dataPoint.addEntities({
@@ -913,7 +1001,7 @@ dataPoint.addEntities({
     // points to a NON Array value
     value: '$a',
     after: isArray(),
-    error: (acc, next) => {
+    error: (acc) => {
       // prints out the error message generated by
       // isArray function
       console.log(acc.value.message) 
@@ -922,7 +1010,7 @@ dataPoint.addEntities({
 
       // passing a value as the second argument
       // will stop the propagation of the error
-      next(null, [])
+      return []
     }
   }
 })
@@ -986,10 +1074,10 @@ Example at: [examples/entity-entry-error-rethrow.js](examples/entity-entry-error
 Let's resolve to a value, in order to prevent the transform from failing.
 
 ```js
-const resolveTo = (value) => (erracc, next) => {
+const resolveTo = (value) => (acc) => {
   // since we don't pass the error back
   // it will resolve to the new value
-  next(null, value)
+  return value
 }
 
 dataPoint.addEntities({
@@ -1026,8 +1114,8 @@ The params object is used to pass custom data to your entity. This Object is exp
 On a FunctionReducer:
 
 ```js
-const multiplyValue = (acc, next) => {
-  next(null, acc.value * acc.params.multiplier)
+const multiplyValue = (acc) => {
+  return acc.value * acc.params.multiplier
 }
 
 dataPoint.addEntities({
@@ -1249,8 +1337,8 @@ dataPoint.addEntities({
       qs: {
         // because the key starts with $
         // it will be treated as a TransformExpression
-        $search: (acc, next) => {
-          next(null, acc.value.search)
+        $search: (acc) => {
+          return acc.value.search
         }
       }
     }
@@ -1278,7 +1366,7 @@ This example simply provides the header object through a reducer. One possible u
 dataPoint.addEntities({
   'request:getOrgInfo': {
     url: 'https://api.github.com/orgs/{value}',
-    beforeRequest: (acc, next) => {
+    beforeRequest: (acc) => {
       // acc.value holds reference to request.options
       const options = _.assign({}, acc.value, {
         headers: {
@@ -1286,7 +1374,7 @@ dataPoint.addEntities({
         }
       })
 
-      next(null, options)
+      return options
     }
   }
 })
@@ -1652,9 +1740,8 @@ dataPoint.transform('hash:addValues').then((acc) => {
 You can add multiple reducers to your Hash spec.
 
 ```js
-const toUpperCase = (acc, next) => {
-  const result = acc.value.toUpperCase()
-  next(null, result)
+const toUpperCase = (acc) => {
+  return acc.value.toUpperCase()
 }
 
 dataPoint.addEntities({
@@ -1882,8 +1969,8 @@ dataPoint.addEntities({
     url: 'https://api.github.com/orgs/nodejs/repos'
   },
   'collection:getRepositoryUrl': {
-    filter: (acc, next) => {
-      next(null, acc.value > 100)
+    filter: (acc) => {
+      return acc.value > 100
     }
   }
 })
@@ -1959,9 +2046,9 @@ dataPoint.addEntities({
   },
   'collection:getNodeRepo': {
     before: 'request:repos',
-    find: (acc, next) => {
+    find: (acc) => {
       // notice we are checking against the property -name- 
-      next(null, acc.value.name === 'node')
+      return acc.value.name === 'node'
     }
   }
 })
@@ -1993,8 +2080,8 @@ dataPoint.transform('request:repos | collection:getNodeRepo', {}).then((acc) =>
 Let's refactor the previous Collection.find example:
 
 ```js
-const isEqualTo = (match) => (acc, next) => {
-  next(null, acc.value === match)
+const isEqualTo = (match) => (acct) => {
+  return acc.value === match
 }
 
 dataPoint.addEntities({
@@ -2036,8 +2123,8 @@ dataPoint.transform('request:repos | collection:getNodeRepo', {}).then((acc) =>
 **Get all forks and map them to a Hash entity**
 
 ```js
-const isEqualTo = (match) => (acc, next) => {
-  next(null, acc.value === match)
+const isEqualTo = (match) => (acc) => {
+  return acc.value === match
 }
 
 dataPoint.addEntities({
@@ -2127,18 +2214,16 @@ If no case statement resolves to `truthy`, then the default statement will be us
 
 ```js
 
-case isEqual = (compareTo) => (acc, next) => {
-  next(null, acc.value === compareTo)
+case isEqual = (compareTo) => (acc) => {
+  return acc.value === compareTo
 }
 
-case resolveTo = (value) => (acc, next) => {
-  next(null, value)
+case resolveTo = (value) => (acc) => {
+  return value
 }
 
-case throwError = (message) => (acc, next) => {
-  // next uses Node.js callback style, where first 
-  // argument is error
-  next(new Error(message))
+case throwError = (message) => (acc) => {
+  throw new Error(message)
 }
 
 dataPoint.addEntities({
@@ -2288,7 +2373,7 @@ dataPoint.addEntities({
     }
   },
   'model:multiplyBy': {
-    value: (acc, next) => next(null, acc.value * acc.params.multiplicand),
+    value: (acc) => acc.value * acc.params.multiplicand,
     params: {
       multiplicand: 1
     }
diff --git a/examples/entity-entry-before.js b/examples/entity-entry-before.js
index 9e5567a..6b386d2 100644
--- a/examples/entity-entry-before.js
+++ b/examples/entity-entry-before.js
@@ -1,15 +1,13 @@
 const dataPoint = require('../').create()
 const _ = require('lodash')
 
-const isArray = () => (acc, next) => {
+const isArray = () => acc => {
   if (_.isArray(acc.value)) {
     // if the value is valid, then just pass it along
-    return next(null, acc.value)
+    return acc.value
   }
 
-  // notice how we pass this Error object as the FIRST parameter,
-  // this tells DataPoint there was an error, and treat it as such.
-  next(new Error(`${acc.value} should be an Array`))
+  throw new Error(`${acc.value} should be an Array`)
 }
 
 dataPoint.addEntities({
diff --git a/examples/entity-entry-error-handled.js b/examples/entity-entry-error-handled.js
index 7152440..2cdaeff 100644
--- a/examples/entity-entry-error-handled.js
+++ b/examples/entity-entry-error-handled.js
@@ -1,14 +1,14 @@
 const dataPoint = require('../').create()
 
-const isArray = () => (acc, next) => {
+const isArray = () => acc => {
   if (acc.value instanceof Array) {
     // if the value is valid, then just pass it along
-    return next(null, acc.value)
+    return acc.value
   }
 
   // notice how we pass this Error object as the FIRST parameter,
   // this tells DataPoint there was an error, and treat it as such.
-  next(new Error(`${acc.value} should be an Array`))
+  throw new Error(`${acc.value} should be an Array`)
 }
 
 dataPoint.addEntities({
@@ -16,11 +16,11 @@ dataPoint.addEntities({
     // points to a NON Array value
     value: '$a',
     after: isArray(),
-    error: (acc, next) => {
+    error: acc => {
       console.log('Value is invalid, resolving to empty array')
       // passing a a value as the second argument
       // will stop the propagation of the error
-      next(null, [])
+      return []
     }
   }
 })
diff --git a/examples/entity-entry-error-resolved.js b/examples/entity-entry-error-resolved.js
index 9464c27..df4905e 100644
--- a/examples/entity-entry-error-resolved.js
+++ b/examples/entity-entry-error-resolved.js
@@ -6,7 +6,7 @@ const logError = () => (errCtx, next) => {
 
   // if we wish to bubble it up, then pass it to
   // the next() as the first parameter
-  next(errCtx)
+  next(errCtx.value)
 
   // if we wished not to bubble it, we could pass
   // an empty first param, and a second value to
@@ -14,15 +14,12 @@ const logError = () => (errCtx, next) => {
   // next(null, 30) <-- this is just an example
 }
 
-const isArray = () => (acc, next) => {
-  if (acc.value instanceof Array) {
-    // if the value is valid, then just pass it along
-    return next(null, acc.value)
+const isArray = () => acc => {
+  if (!(acc.value instanceof Array)) {
+    throw new Error(`${acc.value} should be an Array`)
   }
 
-  // notice how we pass this Error object as the FIRST parameter,
-  // this tells DataPoint there was an error, and treat it as such.
-  next(new Error(`${acc.value} should be an Array`))
+  return acc.value
 }
 
 dataPoint.addEntities({
@@ -40,6 +37,6 @@ const input = {
 }
 
 dataPoint.transform('entry:foo', input).catch(error => {
-  console.log('got ya!', error)
-  // got ya!, Error: [object Object] should be an Array
+  console.log('got ya!', error.toString())
+  // got ya! Error: [object Object] should be an Array
 })
diff --git a/examples/entity-entry-error-rethrown.js b/examples/entity-entry-error-rethrown.js
index 385ee04..f4be76b 100644
--- a/examples/entity-entry-error-rethrown.js
+++ b/examples/entity-entry-error-rethrown.js
@@ -1,21 +1,18 @@
 const dataPoint = require('../').create()
 const assert = require('assert')
 
-const isArray = () => (acc, next) => {
-  if (acc.value instanceof Array) {
-    // if the value is valid, then just pass it along
-    return next(null, acc.value)
+const isArray = () => acc => {
+  if (!(acc.value instanceof Array)) {
+    throw new Error(`${acc.value} should be an Array`)
   }
 
-  // notice how we pass this Error object as the FIRST parameter,
-  // this tells DataPoint there was an error, and treat it as such.
-  next(new Error(`${acc.value} should be an Array`))
+  return acc.value
 }
 
-const resolveTo = value => (errCtx, next) => {
+const resolveTo = value => errCtx => {
   // since we dont pass the error back
   // it will resolve to the new value
-  next(null, value)
+  return value
 }
 
 dataPoint.addEntities({
diff --git a/examples/entity-hash-compose.js b/examples/entity-hash-compose.js
index 79c4234..6752e14 100644
--- a/examples/entity-hash-compose.js
+++ b/examples/entity-hash-compose.js
@@ -12,9 +12,9 @@ dataPoint.addEntities({
       },
       {
         addKeys: {
-          urls: (acc, next) => {
+          urls: acc => {
             const value = acc.value
-            next(null, [value.reposUrl, value.eventsUrl])
+            return [value.reposUrl, value.eventsUrl]
           }
         }
       },
diff --git a/examples/entity-request-before-request.js b/examples/entity-request-before-request.js
index 5217703..a3358b0 100644
--- a/examples/entity-request-before-request.js
+++ b/examples/entity-request-before-request.js
@@ -4,7 +4,7 @@ const _ = require('lodash')
 dataPoint.addEntities({
   'request:getOrgInfo': {
     url: 'https://api.github.com/orgs/{value}',
-    beforeRequest: (acc, next) => {
+    beforeRequest: acc => {
       // acc.value holds reference to request.options
       const options = _.assign({}, acc.value, {
         headers: {
@@ -12,7 +12,7 @@ dataPoint.addEntities({
         }
       })
 
-      next(null, options)
+      return options
     }
   }
 })
diff --git a/examples/entity-request-transform-object.js b/examples/entity-request-transform-object.js
index 60e587d..e49a2ec 100644
--- a/examples/entity-request-transform-object.js
+++ b/examples/entity-request-transform-object.js
@@ -7,8 +7,8 @@ dataPoint.addEntities({
       qs: {
         // because the key starts with $
         // it will be treated as a TransformExpression
-        $search: (acc, next) => {
-          next(null, acc.value.search)
+        $search: acc => {
+          return acc.value.search
         }
       }
     }
diff --git a/examples/extend-entity-reusability.js b/examples/extend-entity-reusability.js
index e5dc5db..a787334 100644
--- a/examples/extend-entity-reusability.js
+++ b/examples/extend-entity-reusability.js
@@ -8,7 +8,7 @@ dataPoint.addEntities({
     }
   },
   'model:multiplyBy': {
-    value: (acc, next) => next(null, acc.value * acc.params.multiplicand),
+    value: acc => acc.value * acc.params.multiplicand,
     params: {
       multiplicand: 1
     }
diff --git a/examples/hello-world.js b/examples/hello-world.js
index 2856a4c..fe34b3a 100644
--- a/examples/hello-world.js
+++ b/examples/hello-world.js
@@ -2,13 +2,15 @@ const DataPoint = require('../')
 
 const dataPoint = DataPoint.create()
 
-// Reducer function that appends 'World' to the
-// value of the accumulator
-const reducer = (acc, next) => {
-  next(null, acc.value + ' World')
+// Reducer function that appends
+// 'World' to the value of the
+// accumulator
+const reducer = acc => {
+  return acc.value + ' World'
 }
 
 // applies reducer to input
 dataPoint.transform(reducer, 'Hello').then(acc => {
-  console.log(acc.value) // 'Hello World'
+  // 'Hello World'
+  console.log(acc.value)
 })
diff --git a/examples/reducer-array-closures.js b/examples/reducer-array-closures.js
index 482ed3d..01bba4e 100644
--- a/examples/reducer-array-closures.js
+++ b/examples/reducer-array-closures.js
@@ -1,8 +1,8 @@
 const dataPoint = require('../').create()
 const assert = require('assert')
 
-const addStr = word => (acc, next) => {
-  next(null, acc.value + word)
+const addStr = word => acc => {
+  return acc.value + word
 }
 
 const reducers = [addStr(' World'), addStr('!!')]
diff --git a/examples/reducer-array-functions.js b/examples/reducer-array-functions.js
index 482ed3d..01bba4e 100644
--- a/examples/reducer-array-functions.js
+++ b/examples/reducer-array-functions.js
@@ -1,8 +1,8 @@
 const dataPoint = require('../').create()
 const assert = require('assert')
 
-const addStr = word => (acc, next) => {
-  next(null, acc.value + word)
+const addStr = word => acc => {
+  return acc.value + word
 }
 
 const reducers = [addStr(' World'), addStr('!!')]
diff --git a/examples/reducer-array-mixed-2.js b/examples/reducer-array-mixed-2.js
index 15ba0f5..934b55e 100644
--- a/examples/reducer-array-mixed-2.js
+++ b/examples/reducer-array-mixed-2.js
@@ -9,13 +9,13 @@ const input = {
   }
 }
 
-const multiplyBy = factor => (acc, next) => {
-  next(null, acc.value * factor)
+const multiplyBy = factor => acc => {
+  return acc.value * factor
 }
 
-const getMax = () => (acc, next) => {
+const getMax = () => acc => {
   const result = Math.max.apply(null, acc.value)
-  next(null, result)
+  return result
 }
 
 dataPoint.transform(['$a.b.c', getMax(), multiplyBy(10)], input).then(acc => {
diff --git a/examples/reducer-array-mixed.js b/examples/reducer-array-mixed.js
index b0cb24a..cec1ba0 100644
--- a/examples/reducer-array-mixed.js
+++ b/examples/reducer-array-mixed.js
@@ -7,8 +7,8 @@ const input = {
   }
 }
 
-const toUpperCase = (acc, next) => {
-  next(null, acc.value.toUpperCase())
+const toUpperCase = acc => {
+  return acc.value.toUpperCase()
 }
 
 dataPoint.transform(['$a.b', toUpperCase], input).then(acc => {
diff --git a/examples/reducer-function-closure.js b/examples/reducer-function-closure.js
index 2f8c528..f654148 100644
--- a/examples/reducer-function-closure.js
+++ b/examples/reducer-function-closure.js
@@ -1,8 +1,8 @@
 const dataPoint = require('../').create()
 const assert = require('assert')
 
-const addStr = value => (acc, next) => {
-  next(null, acc.value + value)
+const addStr = value => acc => {
+  return acc.value + value
 }
 
 dataPoint.transform(addStr(' World!!'), 'Hello').then(acc => {
diff --git a/examples/reducer-function-promise.js b/examples/reducer-function-promise.js
new file mode 100644
index 0000000..ab8b7da
--- /dev/null
+++ b/examples/reducer-function-promise.js
@@ -0,0 +1,11 @@
+const dataPoint = require('../').create()
+const assert = require('assert')
+
+const reducer = acc => {
+  return Promise.resolve(acc.value + ' World')
+}
+
+dataPoint.transform(reducer, 'Hello').then(acc => {
+  console.log(acc.value)
+  assert.equal(acc.value, 'Hello World')
+})
diff --git a/examples/reducer-function-sync.js b/examples/reducer-function-sync.js
new file mode 100644
index 0000000..1514226
--- /dev/null
+++ b/examples/reducer-function-sync.js
@@ -0,0 +1,11 @@
+const dataPoint = require('../').create()
+const assert = require('assert')
+
+const reducer = acc => {
+  return acc.value + ' World'
+}
+
+dataPoint.transform(reducer, 'Hello').then(acc => {
+  console.log(acc.value)
+  assert.equal(acc.value, 'Hello World')
+})
diff --git a/examples/reducer-function.js b/examples/reducer-function-with-callback.js
similarity index 100%
rename from examples/reducer-function.js
rename to examples/reducer-function-with-callback.js
diff --git a/lib/resolve/reducer-function.js b/lib/resolve/reducer-function.js
index 85a925d..5575721 100644
--- a/lib/resolve/reducer-function.js
+++ b/lib/resolve/reducer-function.js
@@ -57,9 +57,9 @@ function resolve (filterStore, accumulator, reducerFunction) {
     return Promise.reject(e)
   }
 
-  if (callbackFunction.length !== 2) {
+  if (callbackFunction.length > 2) {
     const e = new Error(
-      `ReducerFunction '${reducerFunction.name}' must have an arity of 2`
+      `ReducerFunction '${reducerFunction.name}' must have at most an arity of 2`
     )
     e.name = 'InvalidArity'
     return Promise.reject(e)
@@ -71,15 +71,23 @@ function resolve (filterStore, accumulator, reducerFunction) {
     reducerFunction
   )
 
-  return new Promise((resolve, reject) => {
-    // callbackFunction is expected to be a Node Style callback function
-    // with the form of (acc, done)
-    callbackFunction(accumulator, (err, value) => {
-      if (err) {
-        return reject(err)
-      }
-      resolve(utils.set(currentAccumulator, 'value', value))
+  if (callbackFunction.length === 2) {
+    // callbackFunction is expected to be a Node Style
+    // callback function with the form of (acc, done)
+    return new Promise((resolve, reject) => {
+      callbackFunction(accumulator, (err, value) => {
+        if (err) {
+          return reject(err)
+        }
+        resolve(utils.set(currentAccumulator, 'value', value))
+      })
     })
+  }
+
+  // callbackFunction is assumed to be either sync
+  // or Promise returned value
+  return Promise.try(callbackFunction.bind(null, accumulator)).then(result => {
+    return utils.set(currentAccumulator, 'value', result)
   })
 }
 


Test patch:
diff --git a/lib/resolve/reducer-function.test.js b/lib/resolve/reducer-function.test.js
index 9445580..d0b27a7 100644
--- a/lib/resolve/reducer-function.test.js
+++ b/lib/resolve/reducer-function.test.js
@@ -37,7 +37,9 @@ describe('resolve#filter.resolve', () => {
       value: testData.a.b.c
     })
 
-    const reducer = reducerFactory.create('test.invalidReducerArity()')
+    const reducer = reducerFactory.create((a, b, c) => {
+      return false
+    })
 
     return resolveFunction
       .resolve(store.filters, accumulator, reducer)
@@ -62,19 +64,49 @@ describe('resolve#filter.resolve', () => {
       })
   })
 
-  test('resolves an actual function (none string)', () => {
+  test('resolves node style callback', () => {
     const accumulator = AccumulatorFactory.create({
-      value: testData.foo
+      value: 'test'
     })
 
     const reducer = reducerFactory.create((acc, done) =>
-      done(null, `${acc.value}one`)
+      done(null, `${acc.value}node`)
+    )
+
+    return resolveFunction
+      .resolve(store.filters, accumulator, reducer)
+      .then(result => {
+        expect(result.value).toBe('testnode')
+      })
+  })
+
+  test('resolves an sync function', () => {
+    const accumulator = AccumulatorFactory.create({
+      value: 'test'
+    })
+
+    const reducer = reducerFactory.create(acc => `${acc.value}sync`)
+
+    return resolveFunction
+      .resolve(store.filters, accumulator, reducer)
+      .then(result => {
+        expect(result.value).toBe('testsync')
+      })
+  })
+
+  test('resolves an promise function', () => {
+    const accumulator = AccumulatorFactory.create({
+      value: 'test'
+    })
+
+    const reducer = reducerFactory.create(acc =>
+      Promise.resolve(`${acc.value}promise`)
     )
 
     return resolveFunction
       .resolve(store.filters, accumulator, reducer)
       .then(result => {
-        expect(result.value).toBe('1one')
+        expect(result.value).toBe('testpromise')
       })
   })