Iterall
🌻 Minimal zero-dependency utilities for using Iterables in all JavaScript environments.
Install / Use
/learn @leebyron/IterallREADME
| Note: These days, almost all browsers in use natively support these protocols and the syntax to use them. Yay! If you do not need to maintain support for older environments, use this native support directly instead of using iterall. | |--|
JavaScript [Iterators][] and [AsyncIterators][] for all!
iterall provides a few crucial utilities for implementing and working with
[Iterables][iterators], [Async Iterables][asynciterators] and
[Array-likes][array-like] in all JavaScript environments, even old versions of
Internet Explorer, in a tiny library weighing well under 1KB when minified
and gzipped.
This is a library for libraries. If your library takes Arrays as input, accept Iterables instead. If your library implements a new data-structure, make it Iterable.
When installed via npm, iterall comes complete with [Flow][] and
[TypeScript][] definition files. Don't want to take the dependency? Feel free to
copy code directly from this repository.
// Limited to only Arrays 😥
if (Array.isArray(thing)) {
thing.forEach(function (item, i) {
console.log('Index: ' + i, item)
})
}
// Accepts all Iterables and Array-likes, in any JavaScript environment! 🎉
var isCollection = require('iterall').isCollection
var forEach = require('iterall').forEach
if (isCollection(thing)) {
forEach(thing, function (item, i) {
console.log('Index: ' + i, item)
})
}
// Accepts all AsyncIterators, in any JavaScript environment! ⏳
var forAwaitEach = require('iterall').forAwaitEach
forAwaitEach(thing, function (item, i) {
console.log('Index: ' + i, item)
}).then(function () {
console.log('Done')
})
Why use Iterators?
For most of JavaScript's history it has provided two collection data-structures:
the Object and the Array. These collections can conceptually describe nearly
all data and so it's no suprise that libraries expecting lists of
things standardized on expecting and checking for an Array. This pattern even
resulted in the addition of a new method in ES5: [Array.isArray()][isarray].
As JavaScript applications grew in complexity, moved to the [server][nodejs] where CPU is a constrained resource, faced new problems and implemented new algorithms, new data-structures are often required. With options from [linked lists][linked list] to [HAMTs][hamt] developers can use what is most efficient and provides the right properties for their program.
However none of these new data-structures can be used in libraries where an
Array is expected, which means developers are often stuck between abandoning
their favorite libraries or limiting their data-structure choices at the cost of
efficiency or usefulness.
To enable many related data-structures to be used interchangably we need a
[protocol][], and luckily for us ES2015 introduced the
[Iteration Protocols][iterators] to describe all list-like data-structures which
can be iterated. That includes not just the new-to-ES2015 [Map][] and [Set][]
collections but also existing ones like [arguments][], [NodeList][] and the
various [TypedArray][], all of which return false for [Array.isArray()][isarray]
and in ES2015 implement the [Iterator protocol][iterators].
While Iterators are defined in ES2015, they do not require ES2015 to work
correctly. In fact, Iterators were first introduced in 2012 in Firefox v17. Rather than using [Symbol.iterator][symbol.iterator], they used the property name "@@iterator" (in fact, the ECMAScript
spec still refers to well-known Symbols using this @@ shorthand). By falling
back to use "@@iterator" when Symbol.iterator is not defined, Iterators can
be both safely defined and used by any version of JavaScript.
Not only were Iterables defined in ES2015, they were also implemented by the
built-in data-structures including [Array][array#@@iterator]. Older JavaScript
environments do not implement Array.prototype[@@iterator](), however this is
only a minor problem. JavaScript has another related and much older protocol:
[Array-like]. A value is "Array-like" if it has a numeric length property and
indexed access, but does not necessarily have methods like .push() or .forEach().
Much like [Array.from][array.from], iterall's forEach() and
createIterator() methods also accept collections which are not Iterable but
are Array-like. This means that iterall can be used with [Array][],
[arguments][], [NodeList][], [TypedArray][] and other Array-like collections
regardless of the JavaScript environment.
When libraries only accept Arrays as input, they stick developers with a tough
choice: limit which data-structures can be used or limit the ability to use that
library. Accepting Iterables removes this false dichotomy, and allows libraries
to be more generally useful. There's no need to limit to ES2015 environments and
bleeding-edge browsers to accept Iterable.
Only using Arrays can limit the efficiency and usefulness of your application
code, but custom data-structures can often feel like a fish out of water in
JavaScript programs, only working with code written specifically for it.
Protocols like Iterable helps these new data-structures work with more
libraries and built-in JavaScript behavior. There's no need to limit to ES2015
environments and bleeding-edge browsers to implement Iterable.
Why use AsyncIterators?
In the same way that Iterator provides a common interface for accessing many
different kinds of data-structures, AsyncIterator provides a common interface
over an asynchronous sequence of values (similar to Stream or Observable).
Async Iterators are not yet an official part of JavaScript, however they're
a "Stage 3" proposal to be added, and browser vendors are
working on adding support.
However, Async Iterators can be both safely defined and used today by
any version of JavaScript, by using the utilities in iterall.
FAQ
Aren't Iterables slower than Arrays? I want the highest performance possible.
Arrays are Iterables. Iterable is a protocol that Arrays adhere to in ES2015.
It's true that creating an Iterator and stepping through it can present some
overhead compared to a simple for-loop or array.forEach. However iterall's
forEach will delegate directly to array.forEach and will use a for-loop for
Array-like objects, ensuring the best performance for Arrays while still
maintaining support for all Iterables.
Should my library functions also return Iterables instead of Arrays? Won't that be limiting?
That could definitely be limiting if you return some generic Iterable where you could have returned an Array, and (depending on context) I wouldn't recommend you stop returning Arrays from functions if that's what you're doing today. However if your functions are returning some collection data-structure that is not an Array, you should certainly consider having them implement the Iterable protocol so they can be more widely useful.
Here are a few examples:
In [React][], render functions are expected to return view trees, where any
node (e.g. a <ul>) can have many children (e.g. many <li>). While it could
expect those children to always be represented as an Array, that would limit
React's usefulness - other data-structures couldn't be used. Instead, React
expects those children to be represented as an Iterable. That allows it to
continue to accept Arrays, but also accept many other data-structures.
[Immutable.js][] implements many new kinds of data-structures (including [HAMT])
all of which implement Iterable, which allows them to be used in many of
JavaScript's built-in functions, but also allows them to be used by many
libraries which accept Iterables, including React. Also, similar to
[Array.from][array.from], Immutable.js's constructors accept not only Arrays,
but any Iterable, allowing you to build any of these new data-structures from
any other data-structure.
Where are all the other functions like
map,filter, andreduce?
Those "higher order" collection functions are awesome, but they don't belong in
this library. Instead this library should be used as a basis for building such
a library (as it should be used for many other libraries). The forEach
function provided by iterall can be used as the underpinning for these.
As an example:
function reduce (collection, reducer, initial) {
var reduced = initial
forEach(collection, function (item) {
reduced = reducer(reduced, item)
})
return reduced
}
How do I break out of a
forEachorforAwaitEachloop early?
While for of and for await of loops allow breaking out of a loop early with
a break statement, the forEach() and forAwaitEach() functions (much like
Array's forEach) do not support early breaking.
Similar to the "higher order" functions described above, this library can be the
basis for this extended behavior. To support early break outs, you can use a
wrapping function supporting early breaking by throwing a BREAK sentinel value
from the callback and using a try/catch block to early break:
const BREAK = {}
function forEachBreakable (collection, callback) {
try {
forEach(collection, callback)
} catch (error) {
if (error !== BREAK) {
throw error
}
}
}
async function forAwaitEachBreakable (collection, call
Related Skills
node-connect
351.8kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
110.9kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
351.8kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
351.8kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
