/** * Copyright (c) 2016, Lee Byron * All rights reserved. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow * @ignore */ /** * [Iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#iterator) * is a *protocol* which describes a standard way to produce a sequence of * values, typically the values of the Iterable represented by this Iterator. * * While described by the [ES2015 version of JavaScript](http://www.ecma-international.org/ecma-262/6.0/#sec-iterator-interface) * it can be utilized by any version of JavaScript. * * @external Iterator * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#iterator|MDN Iteration protocols} */ /** * [Iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#iterable) * is a *protocol* which when implemented allows a JavaScript object to define * their iteration behavior, such as what values are looped over in a * [`for...of`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of) * loop or `iterall`'s `forEach` function. Many [built-in types](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#Builtin_iterables) * implement the Iterable protocol, including `Array` and `Map`. * * While described by the [ES2015 version of JavaScript](http://www.ecma-international.org/ecma-262/6.0/#sec-iterable-interface) * it can be utilized by any version of JavaScript. * * @external Iterable * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#iterable|MDN Iteration protocols} */ // In ES2015 environments, Symbol exists var SYMBOL /*: any */ = typeof Symbol === 'function' ? Symbol : void 0 // In ES2015 (or a polyfilled) environment, this will be Symbol.iterator var SYMBOL_ITERATOR = SYMBOL && SYMBOL.iterator /** * A property name to be used as the name of an Iterable's method responsible * for producing an Iterator, referred to as `@@iterator`. Typically represents * the value `Symbol.iterator` but falls back to the string `"@@iterator"` when * `Symbol.iterator` is not defined. * * Use `$$iterator` for defining new Iterables instead of `Symbol.iterator`, * but do not use it for accessing existing Iterables, instead use * {@link getIterator} or {@link isIterable}. * * @example * * var $$iterator = require('iterall').$$iterator * * function Counter (to) { * this.to = to * } * * Counter.prototype[$$iterator] = function () { * return { * to: this.to, * num: 0, * next () { * if (this.num >= this.to) { * return { value: undefined, done: true } * } * return { value: this.num++, done: false } * } * } * } * * var counter = new Counter(3) * for (var number of counter) { * console.log(number) // 0 ... 1 ... 2 * } * * @type {Symbol|string} */ /*:: declare export var $$iterator: '@@iterator'; */ export var $$iterator = SYMBOL_ITERATOR || '@@iterator' /** * Returns true if the provided object implements the Iterator protocol via * either implementing a `Symbol.iterator` or `"@@iterator"` method. * * @example * * var isIterable = require('iterall').isIterable * isIterable([ 1, 2, 3 ]) // true * isIterable('ABC') // true * isIterable({ length: 1, 0: 'Alpha' }) // false * isIterable({ key: 'value' }) // false * isIterable(new Map()) // true * * @param obj * A value which might implement the Iterable protocol. * @return {boolean} true if Iterable. */ /*:: declare export function isIterable(obj: any): boolean; */ export function isIterable(obj) { return !!getIteratorMethod(obj) } /** * Returns true if the provided object implements the Array-like protocol via * defining a positive-integer `length` property. * * @example * * var isArrayLike = require('iterall').isArrayLike * isArrayLike([ 1, 2, 3 ]) // true * isArrayLike('ABC') // true * isArrayLike({ length: 1, 0: 'Alpha' }) // true * isArrayLike({ key: 'value' }) // false * isArrayLike(new Map()) // false * * @param obj * A value which might implement the Array-like protocol. * @return {boolean} true if Array-like. */ /*:: declare export function isArrayLike(obj: any): boolean; */ export function isArrayLike(obj) { var length = obj != null && obj.length return typeof length === 'number' && length >= 0 && length % 1 === 0 } /** * Returns true if the provided object is an Object (i.e. not a string literal) * and is either Iterable or Array-like. * * This may be used in place of [Array.isArray()][isArray] to determine if an * object should be iterated-over. It always excludes string literals and * includes Arrays (regardless of if it is Iterable). It also includes other * Array-like objects such as NodeList, TypedArray, and Buffer. * * @example * * var isCollection = require('iterall').isCollection * isCollection([ 1, 2, 3 ]) // true * isCollection('ABC') // false * isCollection({ length: 1, 0: 'Alpha' }) // true * isCollection({ key: 'value' }) // false * isCollection(new Map()) // true * * @example * * var forEach = require('iterall').forEach * if (isCollection(obj)) { * forEach(obj, function (value) { * console.log(value) * }) * } * * @param obj * An Object value which might implement the Iterable or Array-like protocols. * @return {boolean} true if Iterable or Array-like Object. */ /*:: declare export function isCollection(obj: any): boolean; */ export function isCollection(obj) { return Object(obj) === obj && (isArrayLike(obj) || isIterable(obj)) } /** * If the provided object implements the Iterator protocol, its Iterator object * is returned. Otherwise returns undefined. * * @example * * var getIterator = require('iterall').getIterator * var iterator = getIterator([ 1, 2, 3 ]) * iterator.next() // { value: 1, done: false } * iterator.next() // { value: 2, done: false } * iterator.next() // { value: 3, done: false } * iterator.next() // { value: undefined, done: true } * * @template T the type of each iterated value * @param {Iterable} iterable * An Iterable object which is the source of an Iterator. * @return {Iterator} new Iterator instance. */ /*:: declare export var getIterator: & (<+TValue>(iterable: Iterable) => Iterator) & ((iterable: mixed) => void | Iterator); */ export function getIterator(iterable) { var method = getIteratorMethod(iterable) if (method) { return method.call(iterable) } } /** * If the provided object implements the Iterator protocol, the method * responsible for producing its Iterator object is returned. * * This is used in rare cases for performance tuning. This method must be called * with obj as the contextual this-argument. * * @example * * var getIteratorMethod = require('iterall').getIteratorMethod * var myArray = [ 1, 2, 3 ] * var method = getIteratorMethod(myArray) * if (method) { * var iterator = method.call(myArray) * } * * @template T the type of each iterated value * @param {Iterable} iterable * An Iterable object which defines an `@@iterator` method. * @return {function(): Iterator} `@@iterator` method. */ /*:: declare export var getIteratorMethod: & (<+TValue>(iterable: Iterable) => (() => Iterator)) & ((iterable: mixed) => (void | (() => Iterator))); */ export function getIteratorMethod(iterable) { if (iterable != null) { var method = (SYMBOL_ITERATOR && iterable[SYMBOL_ITERATOR]) || iterable['@@iterator'] if (typeof method === 'function') { return method } } } /** * Similar to {@link getIterator}, this method returns a new Iterator given an * Iterable. However it will also create an Iterator for a non-Iterable * Array-like collection, such as Array in a non-ES2015 environment. * * `createIterator` is complimentary to `forEach`, but allows a "pull"-based * iteration as opposed to `forEach`'s "push"-based iteration. * * `createIterator` produces an Iterator for Array-likes with the same behavior * as ArrayIteratorPrototype described in the ECMAScript specification, and * does *not* skip over "holes". * * @example * * var createIterator = require('iterall').createIterator * * var myArraylike = { length: 3, 0: 'Alpha', 1: 'Bravo', 2: 'Charlie' } * var iterator = createIterator(myArraylike) * iterator.next() // { value: 'Alpha', done: false } * iterator.next() // { value: 'Bravo', done: false } * iterator.next() // { value: 'Charlie', done: false } * iterator.next() // { value: undefined, done: true } * * @template T the type of each iterated value * @param {Iterable|{ length: number }} collection * An Iterable or Array-like object to produce an Iterator. * @return {Iterator} new Iterator instance. */ /*:: declare export var createIterator: & (<+TValue>(collection: Iterable) => Iterator) & ((collection: {length: number}) => Iterator) & ((collection: mixed) => (void | Iterator)); */ export function createIterator(collection) { if (collection != null) { var iterator = getIterator(collection) if (iterator) { return iterator } if (isArrayLike(collection)) { return new ArrayLikeIterator(collection) } } } // When the object provided to `createIterator` is not Iterable but is // Array-like, this simple Iterator is created. function ArrayLikeIterator(obj) { this._o = obj this._i = 0 } // Note: all Iterators are themselves Iterable. ArrayLikeIterator.prototype[$$iterator] = function() { return this } // A simple state-machine determines the IteratorResult returned, yielding // each value in the Array-like object in order of their indicies. ArrayLikeIterator.prototype.next = function() { if (this._o === void 0 || this._i >= this._o.length) { this._o = void 0 return { value: void 0, done: true } } return { value: this._o[this._i++], done: false } } /** * Given an object which either implements the Iterable protocol or is * Array-like, iterate over it, calling the `callback` at each iteration. * * Use `forEach` where you would expect to use a `for ... of` loop in ES6. * However `forEach` adheres to the behavior of [Array#forEach][] described in * the ECMAScript specification, skipping over "holes" in Array-likes. It will * also delegate to a `forEach` method on `collection` if one is defined, * ensuring native performance for `Arrays`. * * Similar to [Array#forEach][], the `callback` function accepts three * arguments, and is provided with `thisArg` as the calling context. * * Note: providing an infinite Iterator to forEach will produce an error. * * [Array#forEach]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach * * @example * * var forEach = require('iterall').forEach * * forEach(myIterable, function (value, index, iterable) { * console.log(value, index, iterable === myIterable) * }) * * @example * * // ES6: * for (let value of myIterable) { * console.log(value) * } * * // Any JavaScript environment: * forEach(myIterable, function (value) { * console.log(value) * }) * * @template T the type of each iterated value * @param {Iterable|{ length: number }} collection * The Iterable or array to iterate over. * @param {function(T, number, object)} callback * Function to execute for each iteration, taking up to three arguments * @param [thisArg] * Optional. Value to use as `this` when executing `callback`. */ /*:: declare export var forEach: & (<+TValue, TCollection: Iterable>( collection: TCollection, callbackFn: (value: TValue, index: number, collection: TCollection) => any, thisArg?: any ) => void) & (( collection: TCollection, callbackFn: (value: mixed, index: number, collection: TCollection) => any, thisArg?: any ) => void); */ export function forEach(collection, callback, thisArg) { if (collection != null) { if (typeof collection.forEach === 'function') { return collection.forEach(callback, thisArg) } var i = 0 var iterator = getIterator(collection) if (iterator) { var step while (!(step = iterator.next()).done) { callback.call(thisArg, step.value, i++, collection) // Infinite Iterators could cause forEach to run forever. // After a very large number of iterations, produce an error. /* istanbul ignore if */ if (i > 9999999) { throw new TypeError('Near-infinite iteration.') } } } else if (isArrayLike(collection)) { for (; i < collection.length; i++) { if (collection.hasOwnProperty(i)) { callback.call(thisArg, collection[i], i, collection) } } } } } ///////////////////////////////////////////////////// // // // ASYNC ITERATORS // // // ///////////////////////////////////////////////////// /** * [AsyncIterable](https://tc39.github.io/proposal-async-iteration/#sec-asynciterable-interface) * is a *protocol* which when implemented allows a JavaScript object to define * an asynchronous iteration behavior, such as what values are looped over in * a [`for-await-of`](https://tc39.github.io/proposal-async-iteration/#sec-for-in-and-for-of-statements) * loop or `iterall`'s {@link forAwaitEach} function. * * While described as a proposed addition to the [ES2017 version of JavaScript](https://tc39.github.io/proposal-async-iteration/) * it can be utilized by any version of JavaScript. * * @external AsyncIterable * @see {@link https://tc39.github.io/proposal-async-iteration/#sec-asynciterable-interface|Async Iteration Proposal} * @template T The type of each iterated value * @property {function (): AsyncIterator} Symbol.asyncIterator * A method which produces an AsyncIterator for this AsyncIterable. */ /** * [AsyncIterator](https://tc39.github.io/proposal-async-iteration/#sec-asynciterator-interface) * is a *protocol* which describes a standard way to produce and consume an * asynchronous sequence of values, typically the values of the * {@link AsyncIterable} represented by this {@link AsyncIterator}. * * AsyncIterator is similar to Observable or Stream. Like an {@link Iterator} it * also as a `next()` method, however instead of an IteratorResult, * calling this method returns a {@link Promise} for a IteratorResult. * * While described as a proposed addition to the [ES2017 version of JavaScript](https://tc39.github.io/proposal-async-iteration/) * it can be utilized by any version of JavaScript. * * @external AsyncIterator * @see {@link https://tc39.github.io/proposal-async-iteration/#sec-asynciterator-interface|Async Iteration Proposal} */ // In ES2017 (or a polyfilled) environment, this will be Symbol.asyncIterator var SYMBOL_ASYNC_ITERATOR = SYMBOL && SYMBOL.asyncIterator /** * A property name to be used as the name of an AsyncIterable's method * responsible for producing an Iterator, referred to as `@@asyncIterator`. * Typically represents the value `Symbol.asyncIterator` but falls back to the * string `"@@asyncIterator"` when `Symbol.asyncIterator` is not defined. * * Use `$$asyncIterator` for defining new AsyncIterables instead of * `Symbol.asyncIterator`, but do not use it for accessing existing Iterables, * instead use {@link getAsyncIterator} or {@link isAsyncIterable}. * * @example * * var $$asyncIterator = require('iterall').$$asyncIterator * * function Chirper (to) { * this.to = to * } * * Chirper.prototype[$$asyncIterator] = function () { * return { * to: this.to, * num: 0, * next () { * return new Promise(resolve => { * if (this.num >= this.to) { * resolve({ value: undefined, done: true }) * } else { * setTimeout(() => { * resolve({ value: this.num++, done: false }) * }, 1000) * } * }) * } * } * } * * var chirper = new Chirper(3) * for await (var number of chirper) { * console.log(number) // 0 ...wait... 1 ...wait... 2 * } * * @type {Symbol|string} */ /*:: declare export var $$asyncIterator: '@@asyncIterator'; */ export var $$asyncIterator = SYMBOL_ASYNC_ITERATOR || '@@asyncIterator' /** * Returns true if the provided object implements the AsyncIterator protocol via * either implementing a `Symbol.asyncIterator` or `"@@asyncIterator"` method. * * @example * * var isAsyncIterable = require('iterall').isAsyncIterable * isAsyncIterable(myStream) // true * isAsyncIterable('ABC') // false * * @param obj * A value which might implement the AsyncIterable protocol. * @return {boolean} true if AsyncIterable. */ /*:: declare export function isAsyncIterable(obj: any): boolean; */ export function isAsyncIterable(obj) { return !!getAsyncIteratorMethod(obj) } /** * If the provided object implements the AsyncIterator protocol, its * AsyncIterator object is returned. Otherwise returns undefined. * * @example * * var getAsyncIterator = require('iterall').getAsyncIterator * var asyncIterator = getAsyncIterator(myStream) * asyncIterator.next().then(console.log) // { value: 1, done: false } * asyncIterator.next().then(console.log) // { value: 2, done: false } * asyncIterator.next().then(console.log) // { value: 3, done: false } * asyncIterator.next().then(console.log) // { value: undefined, done: true } * * @template T the type of each iterated value * @param {AsyncIterable} asyncIterable * An AsyncIterable object which is the source of an AsyncIterator. * @return {AsyncIterator} new AsyncIterator instance. */ /*:: declare export var getAsyncIterator: & (<+TValue>(asyncIterable: AsyncIterable) => AsyncIterator) & ((asyncIterable: mixed) => (void | AsyncIterator)); */ export function getAsyncIterator(asyncIterable) { var method = getAsyncIteratorMethod(asyncIterable) if (method) { return method.call(asyncIterable) } } /** * If the provided object implements the AsyncIterator protocol, the method * responsible for producing its AsyncIterator object is returned. * * This is used in rare cases for performance tuning. This method must be called * with obj as the contextual this-argument. * * @example * * var getAsyncIteratorMethod = require('iterall').getAsyncIteratorMethod * var method = getAsyncIteratorMethod(myStream) * if (method) { * var asyncIterator = method.call(myStream) * } * * @template T the type of each iterated value * @param {AsyncIterable} asyncIterable * An AsyncIterable object which defines an `@@asyncIterator` method. * @return {function(): AsyncIterator} `@@asyncIterator` method. */ /*:: declare export var getAsyncIteratorMethod: & (<+TValue>(asyncIterable: AsyncIterable) => (() => AsyncIterator)) & ((asyncIterable: mixed) => (void | (() => AsyncIterator))); */ export function getAsyncIteratorMethod(asyncIterable) { if (asyncIterable != null) { var method = (SYMBOL_ASYNC_ITERATOR && asyncIterable[SYMBOL_ASYNC_ITERATOR]) || asyncIterable['@@asyncIterator'] if (typeof method === 'function') { return method } } } /** * Similar to {@link getAsyncIterator}, this method returns a new AsyncIterator * given an AsyncIterable. However it will also create an AsyncIterator for a * non-async Iterable as well as non-Iterable Array-like collection, such as * Array in a pre-ES2015 environment. * * `createAsyncIterator` is complimentary to `forAwaitEach`, but allows a * buffering "pull"-based iteration as opposed to `forAwaitEach`'s * "push"-based iteration. * * `createAsyncIterator` produces an AsyncIterator for non-async Iterables as * described in the ECMAScript proposal [Async-from-Sync Iterator Objects](https://tc39.github.io/proposal-async-iteration/#sec-async-from-sync-iterator-objects). * * > Note: Creating `AsyncIterator`s requires the existence of `Promise`. * > While `Promise` has been available in modern browsers for a number of * > years, legacy browsers (like IE 11) may require a polyfill. * * @example * * var createAsyncIterator = require('iterall').createAsyncIterator * * var myArraylike = { length: 3, 0: 'Alpha', 1: 'Bravo', 2: 'Charlie' } * var iterator = createAsyncIterator(myArraylike) * iterator.next().then(console.log) // { value: 'Alpha', done: false } * iterator.next().then(console.log) // { value: 'Bravo', done: false } * iterator.next().then(console.log) // { value: 'Charlie', done: false } * iterator.next().then(console.log) // { value: undefined, done: true } * * @template T the type of each iterated value * @param {AsyncIterable|Iterable|{ length: number }} source * An AsyncIterable, Iterable, or Array-like object to produce an Iterator. * @return {AsyncIterator} new AsyncIterator instance. */ /*:: declare export var createAsyncIterator: & (<+TValue>( collection: Iterable | TValue> | AsyncIterable ) => AsyncIterator) & ((collection: {length: number}) => AsyncIterator) & ((collection: mixed) => (void | AsyncIterator)); */ export function createAsyncIterator(source) { if (source != null) { var asyncIterator = getAsyncIterator(source) if (asyncIterator) { return asyncIterator } var iterator = createIterator(source) if (iterator) { return new AsyncFromSyncIterator(iterator) } } } // When the object provided to `createAsyncIterator` is not AsyncIterable but is // sync Iterable, this simple wrapper is created. function AsyncFromSyncIterator(iterator) { this._i = iterator } // Note: all AsyncIterators are themselves AsyncIterable. AsyncFromSyncIterator.prototype[$$asyncIterator] = function() { return this } // A simple state-machine determines the IteratorResult returned, yielding // each value in the Array-like object in order of their indicies. AsyncFromSyncIterator.prototype.next = function(value) { return unwrapAsyncFromSync(this._i, 'next', value) } AsyncFromSyncIterator.prototype.return = function(value) { return this._i.return ? unwrapAsyncFromSync(this._i, 'return', value) : Promise.resolve({ value: value, done: true }) } AsyncFromSyncIterator.prototype.throw = function(value) { return this._i.throw ? unwrapAsyncFromSync(this._i, 'throw', value) : Promise.reject(value) } function unwrapAsyncFromSync(iterator, fn, value) { var step return new Promise(function(resolve) { step = iterator[fn](value) resolve(step.value) }).then(function(value) { return { value: value, done: step.done } }) } /** * Given an object which either implements the AsyncIterable protocol or is * Array-like, iterate over it, calling the `callback` at each iteration. * * Use `forAwaitEach` where you would expect to use a [for-await-of](https://tc39.github.io/proposal-async-iteration/#sec-for-in-and-for-of-statements) loop. * * Similar to [Array#forEach][], the `callback` function accepts three * arguments, and is provided with `thisArg` as the calling context. * * > Note: Using `forAwaitEach` requires the existence of `Promise`. * > While `Promise` has been available in modern browsers for a number of * > years, legacy browsers (like IE 11) may require a polyfill. * * @example * * var forAwaitEach = require('iterall').forAwaitEach * * forAwaitEach(myIterable, function (value, index, iterable) { * console.log(value, index, iterable === myIterable) * }) * * @example * * // ES2017: * for await (let value of myAsyncIterable) { * console.log(await doSomethingAsync(value)) * } * console.log('done') * * // Any JavaScript environment: * forAwaitEach(myAsyncIterable, function (value) { * return doSomethingAsync(value).then(console.log) * }).then(function () { * console.log('done') * }) * * @template T the type of each iterated value * @param {AsyncIterable|Iterable | T>|{ length: number }} source * The AsyncIterable or array to iterate over. * @param {function(T, number, object)} callback * Function to execute for each iteration, taking up to three arguments * @param [thisArg] * Optional. Value to use as `this` when executing `callback`. */ /*:: declare export var forAwaitEach: & (<+TValue, TCollection: Iterable | TValue> | AsyncIterable>( collection: TCollection, callbackFn: (value: TValue, index: number, collection: TCollection) => any, thisArg?: any ) => Promise) & (( collection: TCollection, callbackFn: (value: mixed, index: number, collection: TCollection) => any, thisArg?: any ) => Promise); */ export function forAwaitEach(source, callback, thisArg) { var asyncIterator = createAsyncIterator(source) if (asyncIterator) { var i = 0 return new Promise(function(resolve, reject) { function next() { asyncIterator .next() .then(function(step) { if (!step.done) { Promise.resolve(callback.call(thisArg, step.value, i++, source)) .then(next) .catch(reject) } else { resolve() } // Explicitly return null, silencing bluebird-style warnings. return null }) .catch(reject) // Explicitly return null, silencing bluebird-style warnings. return null } next() }) } }