diff --git a/.travis.yml b/.travis.yml index 05d299e..335f4f4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,3 +2,4 @@ language: node_js node_js: - "0.10" - "0.11" + - "0.12" diff --git a/lib/index.js b/lib/index.js index 3487f07..7cc2223 100755 --- a/lib/index.js +++ b/lib/index.js @@ -49,10 +49,19 @@ var Decoder = require('string_decoder').StringDecoder; * **Promise -** Accepts an ES6 / jQuery style promise and returns a * Highland Stream which will emit a single value (or an error). * + * **Iterator -** Accepts an ES6 style iterator that implements the [iterator protocol] + * (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#The_.22iterator.22_protocol): + * yields all the values from the iterator using its `next()` method and terminates when the + * iterator's done value returns true. + * + * **Iterable -** Accepts an object that implements the [iterable protocol] + * (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#The_.22iterable.22_protocol), + * i.e., contains a method that returns an object that conforms to the iterator protocol. + * * @id _(source) * @section Stream Objects * @name _(source) - * @param {Array | Function | Readable Stream | Promise} source - (optional) source to take values from from + * @param {Array | Function | Readable Stream | Promise | Iterator | Iterable} source - (optional) source to take values from from * @api public * * // from an Array @@ -79,6 +88,16 @@ var Decoder = require('string_decoder').StringDecoder; * * // from a Promise object * var foo = _($.getJSON('/api/foo')); + * + * //from an iterator + * var map = new Map([['a', 1], ['b', 2]]); + * var bar = _(map.values()).toArray(_.log) + * //=> [1, 2] + * + * //from an iterable + * var set = new Set([['a', 1], ['b', 2]]); + * var bar = _(set).toArray(_.log) + * //=> [ [ 'a', 1 ], [ 'b', 2 ] ] */ /*eslint-disable no-multi-spaces */ @@ -340,6 +359,51 @@ _.seq = function () { }; }; +function promiseGenerator(promise) { + return _(function (push) { + promise.then(function (value) { + push(null, value); + return push(null, nil); + }, + function (err) { + push(err); + return push(null, nil); + }); + }); +} + +function iteratorGenerator(it) { + return _(function (push, next) { + var iterElem, iterErr; + try { + iterElem = it.next(); + } + catch (err) { + iterErr = err; + } + + if (iterErr) { + push(iterErr); + push(null, _.nil); + } + else if (iterElem.done) { + if (iterElem.value !== void 0) { + // generators can return a final + // value on completion using return + // keyword otherwise value will be + // undefined + push(null, iterElem.value); + } + push(null, _.nil); + } + else { + push(null, iterElem.value); + next(); + } + + }); +} + /** * Actual Stream constructor wrapped the the main exported function */ @@ -407,7 +471,7 @@ function Stream(/*optional*/xs, /*optional*/ee, /*optional*/mappingHint) { else if (_.isArray(xs)) { self._incoming = xs.concat([nil]); } - else if (typeof xs === 'function') { + else if (_.isFunction(xs)) { this._generator = xs; this._generator_push = function (err, x) { if (self._nil_seen) { @@ -448,17 +512,15 @@ function Stream(/*optional*/xs, /*optional*/ee, /*optional*/mappingHint) { } else if (_.isObject(xs)) { if (_.isFunction(xs.then)) { - // probably a promise - return _(function (push) { - xs.then(function (value) { - push(null, value); - return push(null, nil); - }, - function (err) { - push(err); - return push(null, nil); - }); - }); + //probably a promise + return promiseGenerator(xs); + } + else if (_.isFunction(xs.next)) { + //probably an iterator + return iteratorGenerator(xs); + } + else if (typeof _global.Symbol !== 'undefined' && xs[_global.Symbol.iterator]) { + return iteratorGenerator(xs[_global.Symbol.iterator]()); } else { // write any errors into the stream diff --git a/test/test.js b/test/test.js index a7a07ff..72303e4 100755 --- a/test/test.js +++ b/test/test.js @@ -346,6 +346,138 @@ exports['constructor from promise - errors'] = function (test) { }); }; +function createTestIterator(array, error, lastVal) { + var count = 0, + length = array.length; + return { + next: function() { + if (count < length) { + if (error && count === 2) { + throw error; + } + var iterElem = { + value: array[count], done: false + }; + count++; + return iterElem; + } + else { + return { + value: lastVal, done: true + } + } + } + }; +} + +exports['constructor from iterator'] = function (test) { + test.expect(1); + _(createTestIterator([1, 2, 3, 4, 5])).toArray(function (xs) { + test.same(xs, [1, 2, 3, 4, 5]); + }); + test.done(); +}; + +exports['constructor from iterator - error'] = function (test) { + test.expect(2); + _(createTestIterator([1, 2, 3, 4, 5], new Error('Error at index 2'))).errors(function (err) { + test.equals(err.message, 'Error at index 2'); + }).toArray(function (xs) { + test.same(xs, [1, 2]); + }); + test.done(); +}; + +exports['constructor from iterator - final return falsy'] = function (test) { + test.expect(1); + _(createTestIterator([1, 2, 3, 4, 5], void 0, 0)).toArray(function (xs) { + test.same(xs, [1, 2, 3, 4, 5, 0]); + }); + test.done(); +}; + +//ES6 iterators Begin +if (global.Map && global.Symbol) { + + exports['constructor from Map'] = function (test) { + test.expect(1); + var map = new Map(); + map.set('a', 1); + map.set('b', 2); + map.set('c', 3); + + _(map).toArray(function (xs) { + test.same(xs, [ [ 'a', 1 ], [ 'b', 2 ], [ 'c', 3 ] ]); + }); + test.done(); + }; + + exports['constructor from Map iterator'] = function (test) { + test.expect(1); + var map = new Map(); + map.set('a', 1); + map.set('b', 2); + map.set('c', 3); + + _(map.entries()).toArray(function (xs) { + test.same(xs, [ [ 'a', 1 ], [ 'b', 2 ], [ 'c', 3 ] ]); + }); + test.done(); + }; + + exports['constructor from empty Map iterator'] = function (test) { + test.expect(1); + var map = new Map(); + + _(map.entries()).toArray(function (xs) { + test.same(xs, []); + }); + test.done(); + }; + +} + +if (global.Set && global.Symbol) { + + exports['constructor from Set'] = function (test) { + test.expect(1); + var sett = new Set(); + sett.add('a', 1); + sett.add('b', 2); + sett.add('c', 3); + + _(sett).toArray(function (xs) { + test.same(xs, ['a', 'b', 'c']); + }); + test.done(); + }; + + exports['constructor from Set iterator'] = function (test) { + test.expect(1); + var sett = new Set(); + sett.add('a', 1); + sett.add('b', 2); + sett.add('c', 3); + + _(sett.values()).toArray(function (xs) { + test.same(xs, ['a', 'b', 'c']); + }); + test.done(); + }; + + exports['constructor from empty Map iterator'] = function (test) { + test.expect(1); + var sett = new Set(); + + _(sett.values()).toArray(function (xs) { + test.same(xs, []); + }); + test.done(); + }; + +} +//ES6 iterators End + exports['if no consumers, buffer data'] = function (test) { var s = _(); test.equal(s.paused, true);