How Lodash Filter Handles Async Generators
This article examines how the Lodash utility library processes
asynchronous generators passed to its _.filter method.
Because Lodash is designed exclusively for synchronous data processing,
passing an asynchronous generator does not yield the expected filtered
stream or promise; instead, Lodash treats the generator object as a
plain, empty object, resulting in an empty array.
The Synchronous Architecture of Lodash
Lodash’s _.filter is fundamentally synchronous. It
accepts a collection (such as an array or a plain object) and an
iteratee function. Internally, Lodash inspects the collection to
determine how to traverse it:
- If the collection has a valid numeric
lengthproperty (like an Array or array-like object), Lodash uses index-based traversal. - If the collection lacks a
lengthproperty, Lodash treats it as an object and traverses its own enumerable string-keyed properties using an internal implementation similar tofor...inorObject.keys.
Lodash does not support the ECMAScript asynchronous iteration
protocol (Symbol.asyncIterator), which asynchronous
generators rely on.
Why Async Generators Return an Empty Array
When an async generator function is invoked, it returns an
AsyncGenerator object. This object does not contain a
length property, nor does it have enumerable own
properties. Instead, it exposes methods like next(),
return(), and throw() via its prototype
chain.
Because the generator object has no length property,
_.filter routes it through its internal object-iteration
path. When Lodash attempts to iterate over the object’s own enumerable
properties, it finds none. Consequently:
- The filtering loop terminates immediately without executing any iterations.
- The iteratee/predicate function is never called.
- The generator is never prompted to yield values via its
.next()method. _.filterimmediately and synchronously returns an empty array ([]).
The Problem with Asynchronous Predicates
Even if values could be extracted, _.filter cannot
handle asynchronous predicates. If a predicate returns a
Promise, Lodash evaluates the returned Promise object
directly in a synchronous boolean context. Because all objects
(including unresolved Promises) are truthy in JavaScript, any async
predicate would cause every evaluated item to pass the filter, rather
than waiting for the resolved boolean value.
Proper Alternatives for Filtering Async Generators
To filter items yielded by an asynchronous generator, native JavaScript constructs must be used instead of Lodash:
async function* filterAsyncGenerator(asyncGen, predicate) {
for await (const item of asyncGen) {
if (await predicate(item)) {
yield item;
}
}
}Alternatively, if the data set is finite and memory allows, you can
collect the asynchronous generator's items into a standard array first
using a for await...of loop, and then pass that populated
array to _.filter synchronously.