Gathering detailed insights and metrics for event-iterator
Gathering detailed insights and metrics for event-iterator
Gathering detailed insights and metrics for event-iterator
Gathering detailed insights and metrics for event-iterator
Convert event emitters and event targets to ES async iterators
npm install event-iterator
Typescript
Module System
Node Version
NPM Version
TypeScript (76.28%)
JavaScript (23.72%)
Total Downloads
10,436,107
Last Day
15,231
Last Week
84,453
Last Month
346,970
Last Year
2,728,742
93 Stars
43 Commits
10 Forks
4 Watchers
1 Branches
5 Contributors
Updated on Apr 27, 2025
Latest Version
2.0.0
Package Id
event-iterator@2.0.0
Size
10.48 kB
NPM Version
6.11.3
Node Version
12.12.0
Published on
Jun 12, 2020
Cumulative downloads
Total Downloads
Last Day
7.4%
15,231
Compared to previous day
Last Week
8.6%
84,453
Compared to previous week
Last Month
-2%
346,970
Compared to previous month
Last Year
55.7%
2,728,742
Compared to previous year
EventIterator
is a small module that greatly simplifies converting event
emitters, event targets, and similar objects into EcmaScript async iterators. It
works in browser and Node.js environments.
As a bonus you get utility functions:
subscribe
to subscribe to events on a DOM event target with an async iteratorstream
to consume data from a Node.js readable stream as an async iteratorFor client-side browser events:
1import "core-js/es7/symbol" /* If necessary */ 2import {subscribe} from "event-iterator" 3const element = document.querySelector("a.example") 4 5for await (const click of subscribe.call(element, "click")) { 6 /* Asynchronously iterate over click events on the element. */ 7}
For server-side Node.js events:
1import "core-js/es7/symbol" /* If necessary */ 2import {stream} from "event-iterator" 3const file = require("fs").createReadStream("example-file") 4 5for await (const chunk of stream.call(file)) { 6 /* Asynchronously iterate over buffer chunks read from file. */ 7}
Let's look at how subscribe()
and stream()
are implemented.
For client-side browser events:
1import "core-js/es7/symbol" /* If necessary */ 2import {EventIterator} from "event-iterator" 3 4export function subscribe(event, options) { 5 /* "this" refers to a DOM event target. */ 6 return new EventIterator( 7 ({push}) => { 8 this.addEventListener(event, push, options) 9 return () => this.removeEventListener(event, push, options) 10 } 11 ) 12}
For server-side Node.js events:
1import "core-js/es7/symbol" /* If necessary */ 2import {EventIterator} from "event-iterator" 3 4export function stream() { 5 /* "this" refers to a Node.js readable stream. */ 6 return new EventIterator( 7 queue => { 8 this.addListener("data", queue.push) 9 this.addListener("close", queue.stop) 10 this.addListener("error", queue.fail) 11 12 queue.on("highWater", () => this.pause()) 13 queue.on("lowWater", () => this.resume()) 14 15 return () => { 16 this.removeListener("data", queue.push) 17 this.removeListener("close", queue.stop) 18 this.removeListener("error", queue.fail) 19 this.destroy() 20 } 21 } 22 ) 23}
If you cannot reasonably consume all emitted events with your async iterator;
the internal EventIterator
queue can fill up indefinitely.
A warning will be emitted when the queue reaches the high water mark (100 items by default).
However, if you are able to control the event stream then you can listen to the
highWater
, and lowWater
events to exert backpressure.
When these events are emitted can be changed or disabled by setting
highWaterMark
and lowWaterMark
in the options of the EventIterator
constructor.
1import {EventIterator} from "event-iterator" 2 3const eventIterator = new EventIterator( 4 ({push, on}) => { 5 const file = require("fs").createReadStream("example-file") 6 file.on("data", push) 7 on("highWater", () => file.pause()) 8 on("lowWater", () => file.resume()) 9 return () => file.removeListener("data", push) 10 }, 11 {highWaterMark: 10, lowWaterMark: 5} 12)
Create a new event iterator with new EventIterator(listen)
. This
object implements the async iterator protocol by having a Symbol.asyncIterator
property.
Note: you must set up any Symbol.asyncIterator
polyfills before importing
EventIterator
.
The listen
handler is called every time a new iterator is created to set up
your event listeners. The optional remove
handler is called when the event
listeners need to be removed. The listen
handler returns the remove
handler, making it easy to call addListener
/removeListener
or similar
functions.
Type definitions:
1export interface Queue<T> { 2 push(value: T): void 3 stop(): void 4 fail(error: Error): void 5 on(event: "highWater" | "lowWater", fn: () => void) 6} 7 8export type RemoveHandler = () => void 9export type ListenHandler<T> = (queue: Queue<T>) => void | RemoveHandler 10 11/* High water mark defaults to 100. Set to undefined to disable warnings. */ 12interface EventIteratorOptions = { 13 highWaterMark?: number, 14 lowWaterMark?: number, 15} 16 17class EventIterator<T> { 18 constructor(ListenHandler<T>, options?: EventIteratorOptions) 19 20 [Symbol.asyncIterator](): AsyncIterator<T> 21}
The EventIterator
class is an adapter to transform any browser or Node.js
event emitter into an async iterator that iterates over events.
Imagine you have a bunch of text files and in Node.js and you want to decide whether they are longer or shorter than a certain number of lines. The files should not be binary to avoid cluttering the results. To be more specific, we want a function that will:
true
if the number of lines is 1000 or greaterfalse
if the number of lines is less than 1000A naive solution would look like this:
1function countLines(buffer) { 2 const str = buffer.toString() 3 if (str.match("\0")) throw new Error("Binary file!") 4 return (str.match(/\n/g) || []).length 5} 6 7function isLongTextFile(file) { 8 let lines = 1 9 10 return new Promise((resolve, reject) => { 11 file.on("data", chunk => { 12 lines += countLines(chunk) 13 }) 14 15 file.on("end", () => { 16 resolve(lines >= 1000) 17 }) 18 19 file.on("error", err => { 20 reject(err) 21 }) 22 }) 23} 24 25isLongTextFile(fs.createReadStream("...")).then(console.log)
Unfortunately, this solution has some problems:
So we improve our solution, and we arrive at something like this:
1function isLongTextFile(file) { 2 let lines = 1 3 4 const isLong = n => n >= 1000 5 6 return new Promise((resolve, reject) => { 7 file.on("data", chunk => { 8 try { 9 lines += countLines(chunk) 10 if (isLong(lines)) { 11 file.close() 12 resolve(true) 13 } 14 } catch (err) { 15 file.destroy() 16 reject(err) 17 } 18 }) 19 20 file.on("end", () => { 21 resolve(isLong(lines)) 22 }) 23 24 file.on("error", err => { 25 reject(err) 26 }) 27 }) 28} 29 30isLongTextFile(fs.createReadStream("...")).then(console.log)
This works and we're happy to have solved the problem!
But what if there were a nicer way to do this? Async iterators sure seem like a nice fit for this problem. They are a stage 3 EcmaScript proposal and can be used by using TypeScript or Babel.
A similar solution using async iterators could look like this:
1function async isLongTextFile(file) { 2 let lines = 1 3 for await (const chunk of stream.call(file)) { // or file::stream() 4 lines += countLines(chunk) 5 if (lines > 1000) return true 6 } 7 return false 8} 9 10isLongTextFile(fs.createReadStream("...")).then(console.log)
The question is: how do you create an async iterator from a readable stream? Conceptually they are very similar; they both:
Async iterators have a few additional advantages that translate in simpler code:
So how do you transform a readble stream into an async iterator? With an EventIterator
.
We can define the stream
function above as:
1import {EventIterator} from "event-iterator" 2 3function stream() { 4 return new EventIterator( 5 ({ push, stop, fail }) => { 6 this.addListener("data", push) 7 this.addListener("end", stop) 8 this.addListener("error", fail) 9 10 return () => { 11 this.removeListener("data", push) 12 this.removeListener("end", stop) 13 this.removeListener("error", fail) 14 this.destroy() 15 } 16 } 17 ) 18}
The EventIterator
takes care of:
Why create an abstract EventIterator
that requires you to define your own
integration code? Several reasons:
Copyright (c) 2017-2020 Rolf Timmermans
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
No vulnerabilities found.
Reason
no dangerous workflow patterns detected
Reason
no binaries found in the repo
Reason
0 existing vulnerabilities detected
Reason
Found 3/19 approved changesets -- score normalized to 1
Reason
detected GitHub workflow tokens with excessive permissions
Details
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
Reason
project is not fuzzed
Details
Reason
license file not detected
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Score
Last Scanned on 2025-04-28
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More