|
| 1 | +# JavaScript [Iterators][] for all! |
| 2 | + |
| 3 | +[](https://travis-ci.org/leebyron/iterall) [](https://coveralls.io/github/leebyron/iterall?branch=master)  |
| 4 | + |
| 5 | +`iterall` provides a few crucial utilities for implementing and working with |
| 6 | +[Iterables][iterators] and [Array-likes][array-like] in all JavaScript |
| 7 | +environments, even old versions of Internet Explorer, in a tiny library weighing |
| 8 | +well under 1KB when minified and gzipped. |
| 9 | + |
| 10 | +This is a library for libraries. If your library takes Arrays as input, accept |
| 11 | +Iterables instead. If your library implements a new data-structure, make |
| 12 | +it Iterable. |
| 13 | + |
| 14 | +When installed via `npm`, `iterall` comes complete with [flow][] and |
| 15 | +[TypeScript][] definition files. Don't want to take the dependency? Feel free to |
| 16 | +copy code directly from this repository. |
| 17 | + |
| 18 | +```js |
| 19 | +// Limited to only Arrays 😥 |
| 20 | +if (Array.isArray(thing)) { |
| 21 | + thing.forEach(function (item, i) { |
| 22 | + console.log('Index: ' + i, item) |
| 23 | + }) |
| 24 | +} |
| 25 | + |
| 26 | +// Accepts all Iterables and Array-likes, in any JavaScript environment! 🎉 |
| 27 | +var isCollection = require('iterall').isCollection |
| 28 | +var forEach = require('iterall').forEach |
| 29 | + |
| 30 | +if (isCollection(thing)) { |
| 31 | + forEach(thing, function (item, i) { |
| 32 | + console.log('Index: ' + i, item) |
| 33 | + }) |
| 34 | +} |
| 35 | +``` |
| 36 | + |
| 37 | +## Why use Iterators? |
| 38 | + |
| 39 | +For most of JavaScript's history it has provided two collection data-structures: |
| 40 | +the `Object` and the `Array`. These collections can conceptually describe nearly |
| 41 | +all data and so it's no suprise that libraries expecting lists of |
| 42 | +things standardized on expecting and checking for an Array. This pattern even |
| 43 | +resulted in the addition of a new method in ES5: [`Array.isArray()`][isarray]. |
| 44 | + |
| 45 | +As JavaScript applications grew in complexity, moved to the [server][nodejs] |
| 46 | +where CPU is a constrained resource, faced new problems and implemented new |
| 47 | +algorithms, new data-structures are often required. With options from |
| 48 | +[linked lists][linked list] to [HAMTs][hamt] developers can use what is most |
| 49 | +efficient and provides the right properties for their program. |
| 50 | + |
| 51 | +However none of these new data-structures can be used in libraries where an |
| 52 | +`Array` is expected, which means developers are often stuck between abandoning |
| 53 | +their favorite libraries or limiting their data-structure choices at the cost of |
| 54 | +efficiency or usefulness. |
| 55 | + |
| 56 | +To enable many related data-structures to be used interchangably we need a |
| 57 | +_[protocol][]_, and luckily for us ES2015 introduced the |
| 58 | +[Iteration Protocols][iterators] to describe all list-like data-structures which |
| 59 | +can be iterated. That includes not just the new-to-ES2015 [Map][] and [Set][] |
| 60 | +collections but also existing ones like [arguments][], [NodeList][] and the |
| 61 | +various [TypedArray][], all of which return `false` for [`Array.isArray()`][isarray] |
| 62 | +and in ES2015 implement the [Iterator protocol][iterators]. |
| 63 | + |
| 64 | +While Iterators are defined in ES2015, they _do not require_ ES2015 to work |
| 65 | +correctly. In fact, Iterators were first introduced in 2012 in [Firefox v17](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#Iterator_property_and_iterator_symbol). Rather than using [`Symbol.iterator`][symbol.iterator], they used the property name `"@@iterator"` (in fact, the ECMAScript |
| 66 | +spec still refers to well-known `Symbols` using this `@@` shorthand). By falling |
| 67 | +back to use `"@@iterator"` when `Symbol.iterator` is not defined, Iterators can |
| 68 | +be both safely defined and used by _any version of JavaScript_. |
| 69 | + |
| 70 | +Not only were Iterables defined in ES2015, they were also implemented by the |
| 71 | +built-in data-structures including [Array][array#@@iterator]. Older JavaScript |
| 72 | +environments do not implement `Array.prototype[@@iterator]()`, however this is |
| 73 | +only a minor problem. JavaScript has another related and much older protocol: |
| 74 | +[Array-like]. An value is "Array-like" if it has a numeric `length` property and |
| 75 | +indexed access, but does not necessarily have methods like `push` or `forEach`. |
| 76 | +Much like [`Array.from`][array.from], `iterall`'s `forEach()` and |
| 77 | +`createIterator()` methods also accept collections which are not Iterable but |
| 78 | +are Array-like. This means that `iterall` can be used with `Array`, `arguments`, |
| 79 | +`NodeList`, `TypedArray` and other Array-like collections regardless of the |
| 80 | +JavaScript environment. |
| 81 | + |
| 82 | +When libraries only accept Arrays as input, they stick developers with a tough |
| 83 | +choice: limit which data-structures can be used or limit the ability to use that |
| 84 | +library. Accepting Iterables removes this false dichotomy, and allows libraries |
| 85 | +to be more generally useful. There's no need to limit to ES2015 environments and |
| 86 | +bleeding-edge browsers to leverage `Iterables`. |
| 87 | + |
| 88 | +Only using Arrays can limit the efficiency and usefulness of your application |
| 89 | +code, but custom data-structures can often feel like a fish out of water in |
| 90 | +JavaScript programs, only working with code written specifically for it. |
| 91 | +Protocols like `Iterable` helps these new data-structures work with more |
| 92 | +libraries and built-in JavaScript behavior. There's no need to limit to ES2015 |
| 93 | +environments and bleeding-edge browsers to implement `Iterable`. |
| 94 | + |
| 95 | +## API |
| 96 | + |
| 97 | +### $$ITERATOR |
| 98 | + |
| 99 | +A property name to be used as the name of an Iterable's method reponsible |
| 100 | +for producing an Iterator. Typically represents the value `Symbol.iterator`. |
| 101 | + |
| 102 | +`Symbol` is defined in ES2015 environments, however some transitioning |
| 103 | +JavaScript environments, such as older versions of Node define Symbol, but |
| 104 | +do not define `Symbol.iterator`. Older versions of Mozilla Firefox, |
| 105 | +which originally introduced the Iterable protocol, used the string |
| 106 | +value `"@@iterator"`. This string value is used when Symbol.iterator is |
| 107 | +not defined. |
| 108 | + |
| 109 | +Use `$$ITERATOR` for defining new Iterables instead of `Symbol.iterator`, |
| 110 | +but do not use it for accessing existing Iterables, instead use `getIterator` |
| 111 | +or `isIterable`. |
| 112 | + |
| 113 | +**Examples** |
| 114 | + |
| 115 | +```javascript |
| 116 | +var $$ITERATOR = require('iterall').$$ITERATOR |
| 117 | + |
| 118 | +function Counter(to) { |
| 119 | + this.to = to |
| 120 | + this.num = 0 |
| 121 | +} |
| 122 | + |
| 123 | +Counter.prototype[$$ITERATOR] = function () { |
| 124 | + if (this.num >= this.to) { |
| 125 | + return { value: undefined, done: true } |
| 126 | + } |
| 127 | + return { value: this.num++ } |
| 128 | +} |
| 129 | +``` |
| 130 | + |
| 131 | +### isIterable |
| 132 | + |
| 133 | +Returns true if the provided object implements the Iterator protocol via |
| 134 | +either implementing Symbol.iterator or '@@iterator' method. |
| 135 | + |
| 136 | +**Parameters** |
| 137 | + |
| 138 | +- `obj` **Any** A value which might be implement the Iterable protocol. |
| 139 | + |
| 140 | +**Examples** |
| 141 | + |
| 142 | +```javascript |
| 143 | +var isIterable = require('iterall').isIterable |
| 144 | +isIterable([ 1, 2, 3 ]) // true |
| 145 | +isIterable('ABC') // true |
| 146 | +isIterable({ key: 'value' }) // false |
| 147 | +``` |
| 148 | + |
| 149 | +Returns **[boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** true if Iterable. |
| 150 | + |
| 151 | +### isCollection |
| 152 | + |
| 153 | +Returns true if the provided object is an Object (i.e. not a string literal) |
| 154 | +and is either "Array-like" due to having a numeric `length` property, or is |
| 155 | +Iterable. |
| 156 | + |
| 157 | +This may be used in place of `Array.isArray(obj)` to determine if an object |
| 158 | +can be iterated-over. It always excludes string literals and includes Arrays |
| 159 | +(regardless of if it is Iterable). It also includes other Array-like objects |
| 160 | +such as NodeList, TypedArray, and Buffer. It also includes any Object which |
| 161 | +implements the Iterable protocol. |
| 162 | + |
| 163 | +**Parameters** |
| 164 | + |
| 165 | +- `obj` **Any** An Object value which might implement the Iterable or Array-like protocols. |
| 166 | + |
| 167 | +**Examples** |
| 168 | + |
| 169 | +```javascript |
| 170 | +var isCollection = require('iterall').isCollection |
| 171 | +var forEach = require('iterall').forEach |
| 172 | +if (isCollection(obj)) { |
| 173 | + forEach(obj, function (value) { |
| 174 | + console.log(value) |
| 175 | + }) |
| 176 | +} |
| 177 | +``` |
| 178 | + |
| 179 | +Returns **[boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** true if Iterable or Array-like Object. |
| 180 | + |
| 181 | +### getIterator |
| 182 | + |
| 183 | +If the provided object implements the Iterator protocol, its Iterator object |
| 184 | +is returned. Otherwise returns undefined. |
| 185 | + |
| 186 | +**Parameters** |
| 187 | + |
| 188 | +- `iterable` **Iterable<T>** An Iterable object which is the source of an Iterator. |
| 189 | + |
| 190 | +**Examples** |
| 191 | + |
| 192 | +```javascript |
| 193 | +var getIterator = require('iterall').getIterator |
| 194 | +var iterator = getIterator([ 1, 2, 3 ]) |
| 195 | +iterator.next() // { value: 1, done: false } |
| 196 | +iterator.next() // { value: 2, done: false } |
| 197 | +iterator.next() // { value: 3, done: false } |
| 198 | +iterator.next() // { value: undefined, done: true } |
| 199 | +``` |
| 200 | + |
| 201 | +Returns **Iterator<T>** new Iterator instance. |
| 202 | + |
| 203 | +### getIteratorMethod |
| 204 | + |
| 205 | +If the provided object implements the Iterator protocol, the method |
| 206 | +responsible for producing its Iterator object is returned. |
| 207 | + |
| 208 | +This is used in rare cases for performance tuning. This method must be called |
| 209 | +with obj as the contextual this-argument. |
| 210 | + |
| 211 | +**Parameters** |
| 212 | + |
| 213 | +- `iterable` **Iterable<T>** An Iterable object which defines an `@@iterator` method. |
| 214 | + |
| 215 | +**Examples** |
| 216 | + |
| 217 | +```javascript |
| 218 | +var getIteratorMethod = require('iterall').getIteratorMethod |
| 219 | +var myArray = [ 1, 2, 3 ] |
| 220 | +var method = getIteratorMethod(myArray) |
| 221 | +if (method) { |
| 222 | + var iterator = method.call(myArray) |
| 223 | +} |
| 224 | +``` |
| 225 | + |
| 226 | +Returns **function (): Iterator<T>** @@iterator method. |
| 227 | + |
| 228 | +### forEach |
| 229 | + |
| 230 | +Given an object which is either Array-like (by having a numeric length |
| 231 | +property) or implements the Iterable protocol, iterate over it, calling the |
| 232 | +`callback` at each iteration. |
| 233 | + |
| 234 | +Similar to Array#forEach, the `callback` function accepts three arguments, |
| 235 | +and is provided with `thisArg` as the calling context. |
| 236 | + |
| 237 | +`forEach` adheres to the behavior described in the ECMAScript specification, |
| 238 | +skipping over "holes" in Arrays and Array-likes. |
| 239 | + |
| 240 | +Note: providing an infinite Iterator to forEach will produce an error. |
| 241 | + |
| 242 | +**Parameters** |
| 243 | + |
| 244 | +- `collection` **(Iterable<T> | [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<T>)** The Iterable or array to iterate over. |
| 245 | +- `callback` **function (T, [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number), \[[object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)])** Function to execute for each iteration, taking up to three arguments |
| 246 | +- `thisArg` Optional. Value to use as `this` when executing `callback`. |
| 247 | + |
| 248 | +**Examples** |
| 249 | + |
| 250 | +```javascript |
| 251 | +var forEach = require('iterall').forEach |
| 252 | + |
| 253 | +forEach(myIterable, function (value, index) { |
| 254 | + console.log(value, index) |
| 255 | +}) |
| 256 | +``` |
| 257 | + |
| 258 | +### createIterator |
| 259 | + |
| 260 | +Similar to `getIterator(obj)`, this method returns a new Iterator given an |
| 261 | +Iterable. However it will also create an Iterator for a non-Iterable |
| 262 | +Array-like collection, such as Array in a non-ES2015 environment. |
| 263 | + |
| 264 | +`createIterator` is complimentary to `forEach`, but allows a "pull"-based |
| 265 | +iteration as opposed to `forEach`'s "push"-based iteration. |
| 266 | + |
| 267 | +`createIterator` produces an Iterator for Array-likes with the same behavior |
| 268 | +as ArrayIteratorPrototype described in the ECMAScript specification, and |
| 269 | +does _not_ skip over "holes". |
| 270 | + |
| 271 | +**Parameters** |
| 272 | + |
| 273 | +- `collection` **(Iterable<T> | [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<T>)** An Iterable or Array-like object to produce an Iterator. |
| 274 | + |
| 275 | +**Examples** |
| 276 | + |
| 277 | +```javascript |
| 278 | +var createIterator = require('iterall').createIterator |
| 279 | + |
| 280 | +var myArraylike = { length: 3, 0: 'Alpha', 1: 'Bravo', 2: 'Charlie' } |
| 281 | +var iterator = createIterator(myArraylike) |
| 282 | +iterator.next() // { value: 'Alpha', done: false } |
| 283 | +iterator.next() // { value: 'Bravo', done: false } |
| 284 | +iterator.next() // { value: 'Charlie', done: false } |
| 285 | +iterator.next() // { value: undefined, done: true } |
| 286 | +``` |
| 287 | + |
| 288 | +Returns **Iterator<T>** new Iterator instance. |
| 289 | + |
| 290 | +## Contributing |
| 291 | + |
| 292 | +Contributions are welcome and encouraged! |
| 293 | + |
| 294 | +Remember that this library is designed to be small, straight-forward, and |
| 295 | +well-tested. The value of new additional features will be weighed against their |
| 296 | +size. This library also seeks to leverage and mirror the |
| 297 | +[ECMAScript specification][] in its behavior as much as possible and reasonable. |
| 298 | + |
| 299 | +This repository has far more documentation and explaination than code, and it is |
| 300 | +expected that the majority of contributions will come in the form of improving |
| 301 | +these. |
| 302 | + |
| 303 | +<!-- Appendix --> |
| 304 | + |
| 305 | +[arguments]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments |
| 306 | + |
| 307 | +[array#@@iterator]: (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/@@iterator) |
| 308 | + |
| 309 | +[array-like]: http://www.2ality.com/2013/05/quirk-array-like-objects.html |
| 310 | + |
| 311 | +[array.from]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from |
| 312 | + |
| 313 | +[ecmascript specification]: http://www.ecma-international.org/ecma-262/6.0/ |
| 314 | + |
| 315 | +[flow]: https://flowtype.org/ |
| 316 | + |
| 317 | +[hamt]: https://en.wikipedia.org/wiki/Hash_array_mapped_trie |
| 318 | + |
| 319 | +[isarray]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray |
| 320 | + |
| 321 | +[iterators]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols |
| 322 | + |
| 323 | +[linked list]: https://en.wikipedia.org/wiki/Linked_list |
| 324 | + |
| 325 | +[map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map |
| 326 | + |
| 327 | +[nodejs]: https://nodejs.org/ |
| 328 | + |
| 329 | +[nodelist]: https://developer.mozilla.org/en-US/docs/Web/API/NodeList |
| 330 | + |
| 331 | +[protocol]: https://en.wikipedia.org/wiki/Protocol_(object-oriented_programming) |
| 332 | + |
| 333 | +[set]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set |
| 334 | + |
| 335 | +[symbol.iterator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/iterator |
| 336 | + |
| 337 | +[typedarray]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray |
| 338 | + |
| 339 | +[typescript]: http://www.typescriptlang.org/ |
0 commit comments