ARROW-2159: [JS] Support custom predicates

Adds support for defining a custom predicate with callback functions, like so:
```js
table.filter(predicate.custom(
    (idx) => {...},
    (batch) => {...}
));
```

Author: Brian Hulette <brian.hulette@ccri.com>

Closes #1616 from TheNeuralBit/custom-predicate and squashes the following commits:

77f5441 [Brian Hulette] Add test, squash bug
29ecc80 [Brian Hulette] Add CustomPredicate
diff --git a/js/src/Arrow.externs.js b/js/src/Arrow.externs.js
index 21dca8b..de1e653 100644
--- a/js/src/Arrow.externs.js
+++ b/js/src/Arrow.externs.js
@@ -70,6 +70,7 @@
 
 var col = function () {};
 var lit = function () {};
+var custom = function () {};
 
 var Value = function() {};
 /** @type {?} */
@@ -738,4 +739,4 @@
 /** @type {?} */
 VectorVisitor.prototype.visitFixedSizeList;
 /** @type {?} */
-VectorVisitor.prototype.visitMap;
\ No newline at end of file
+VectorVisitor.prototype.visitMap;
diff --git a/js/src/Arrow.ts b/js/src/Arrow.ts
index df37a8f..4a0a2ac 100644
--- a/js/src/Arrow.ts
+++ b/js/src/Arrow.ts
@@ -168,6 +168,7 @@
 export namespace predicate {
     export import col = predicate_.col;
     export import lit = predicate_.lit;
+    export import custom = predicate_.custom;
 
     export import Or = predicate_.Or;
     export import Col = predicate_.Col;
diff --git a/js/src/predicate.ts b/js/src/predicate.ts
index 981ffb1..b177b4f 100644
--- a/js/src/predicate.ts
+++ b/js/src/predicate.ts
@@ -222,5 +222,19 @@
     }
 }
 
+export class CustomPredicate extends Predicate {
+    constructor(private next: PredicateFunc, private bind_: (batch: RecordBatch) => void) {
+        super();
+    }
+
+    bind(batch: RecordBatch) {
+        this.bind_(batch);
+        return this.next;
+    }
+}
+
 export function lit(v: any): Value<any> { return new Literal(v); }
 export function col(n: string): Col<any> { return new Col(n); }
+export function custom(next: PredicateFunc, bind: (batch: RecordBatch) => void) {
+    return new CustomPredicate(next, bind);
+}
diff --git a/js/test/unit/table-tests.ts b/js/test/unit/table-tests.ts
index ffcc8f4..8a43381 100644
--- a/js/test/unit/table-tests.ts
+++ b/js/test/unit/table-tests.ts
@@ -15,11 +15,11 @@
 // specific language governing permissions and limitations
 // under the License.
 
-import Arrow from '../Arrow';
+import Arrow, { RecordBatch } from '../Arrow';
 
 const { predicate, Table } = Arrow;
 
-const { col, lit } = predicate;
+const { col, lit, custom } = predicate;
 
 const F32 = 0, I32 = 1, DICT = 2;
 const test_data = [
@@ -323,6 +323,7 @@
                 expect(table.getColumnIndex('f32')).toEqual(F32);
                 expect(table.getColumnIndex('dictionary')).toEqual(DICT);
             });
+            let get_i32: (idx: number) => number, get_f32: (idx: number) => number;
             const filter_tests = [
                 {
                     name:     `filter on f32 >= 0`,
@@ -364,6 +365,15 @@
                     name:     `filter on f32 <= i32`,
                     filtered: table.filter(col('f32').lteq(col('i32'))),
                     expected: values.filter((row) => row[F32] <= row[I32])
+                }, {
+                    name:     `filter on f32*i32 > 0 (custom predicate)`,
+                    filtered: table.filter(custom(
+                        (idx: number) => (get_f32(idx) * get_i32(idx) > 0),
+                        (batch: RecordBatch) => {
+                            get_f32 = col('f32').bind(batch);
+                            get_i32 = col('i32').bind(batch);
+                        })),
+                    expected: values.filter((row) => (row[F32] as number) * (row[I32] as number) > 0)
                 }
             ];
             for (let this_test of filter_tests) {