{"_id":"event-iterator","_rev":"4573611","name":"event-iterator","description":"Convert event emitters and event targets to ES async iterators","dist-tags":{"latest":"2.0.0"},"maintainers":[{"name":"rolftimmermans","email":""}],"time":{"modified":"2026-04-10T15:35:25.000Z","created":"2017-09-22T09:51:46.459Z","2.0.0":"2020-06-12T12:26:39.825Z","1.2.0":"2019-03-28T11:55:01.446Z","1.1.0":"2019-03-19T19:36:58.676Z","1.0.0":"2017-09-22T09:51:46.459Z"},"users":{},"author":{"name":"Rolf Timmermans","email":"rolftimmermans@voormedia.com"},"repository":{"type":"git","url":"git+https://github.com/rolftimmermans/event-iterator.git"},"versions":{"2.0.0":{"name":"event-iterator","version":"2.0.0","description":"Convert event emitters and event targets to ES async iterators","homepage":"https://github.com/rolftimmermans/event-iterator","license":"MIT","author":{"name":"Rolf Timmermans","email":"rolftimmermans@voormedia.com"},"repository":{"type":"git","url":"git+https://github.com/rolftimmermans/event-iterator.git"},"keywords":["async","async-iterator","event-emitter","event-target","stream","await","for-await","esnext","node","browser"],"main":"lib/node.js","browser":"lib/dom.js","devDependencies":{"@types/chai":">= 0","@types/jsdom":">= 0","@types/mocha":">= 0","@types/node":">= 8.0","@types/sinon":">= 7.5.1","@typescript-eslint/eslint-plugin":"^3.2.0","@typescript-eslint/parser":"^3.2.0","chai":">= 4.1","eslint":"^7.2.0","eslint-config-prettier":"^6.11.0","eslint-plugin-prettier":"^3.1.3","jsdom":">= 11.0","mocha":">= 3.1","prettier":"^2.0.5","ts-node":">= 3.3","typescript":">= 3.3","sinon":">= 7.5.0"},"scripts":{"lint":"eslint src/**/*.ts test/**/*.ts","fmt":"eslint --fix src/**/*.ts test/**/*.ts","test":"mocha --require ts-node/register test/*-test.ts && rm -rf lib && tsc"},"gitHead":"d7699d3d6e8bf3fa82c7cd42dc1a0a44e342b6d9","bugs":{"url":"https://github.com/rolftimmermans/event-iterator/issues"},"_id":"event-iterator@2.0.0","_nodeVersion":"12.12.0","_npmVersion":"6.11.3","dist":{"shasum":"10f06740cc1e9fd6bc575f334c2bc1ae9d2dbf62","size":10728,"noattachment":false,"key":"/event-iterator/-/event-iterator-2.0.0.tgz","tarball":"http://registry.cnpm.dingdandao.com/event-iterator/download/event-iterator-2.0.0.tgz"},"maintainers":[{"name":"rolftimmermans","email":""}],"_npmUser":{"name":"rolftimmermans","email":"r.w.timmermans@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/event-iterator_2.0.0_1591964799691_0.6241952816063658"},"_hasShrinkwrap":false,"publish_time":1591964799825,"_cnpm_publish_time":1591964799825,"_cnpmcore_publish_time":"2021-12-16T18:33:58.062Z"},"1.2.0":{"name":"event-iterator","version":"1.2.0","description":"Convert event emitters and event targets to ES async iterators","homepage":"https://github.com/rolftimmermans/event-iterator","license":"MIT","author":{"name":"Rolf Timmermans","email":"rolftimmermans@voormedia.com"},"repository":{"type":"git","url":"https://github.com/rolftimmermans/event-iterator.git"},"keywords":["async","async-iterator","event-emitter","event-target","stream","await","for-await","esnext","node","browser"],"main":"lib/node.js","browser":"lib/dom.js","devDependencies":{"@types/chai":">= 0","@types/jsdom":">= 0","@types/mocha":">= 0","@types/node":">= 8.0","chai":">= 4.1","jsdom":">= 11.0","mocha":">= 3.1","ts-node":">= 3.3","typescript":">= 3.3"},"scripts":{"test":"mocha test/*-test.ts && rm -rf lib && tsc"},"_id":"event-iterator@1.2.0","dist":{"shasum":"2e71dc6ca56f1cf8ebcb2b9be7fdfd10acabbb76","size":8269,"noattachment":false,"key":"/event-iterator/-/event-iterator-1.2.0.tgz","tarball":"http://registry.cnpm.dingdandao.com/event-iterator/download/event-iterator-1.2.0.tgz"},"maintainers":[{"name":"rolftimmermans","email":""}],"_npmUser":{"name":"rolftimmermans","email":"r.w.timmermans@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/event-iterator_1.2.0_1553774101118_0.7091267914853772"},"_hasShrinkwrap":false,"publish_time":1553774101446,"_cnpm_publish_time":1553774101446,"_cnpmcore_publish_time":"2021-12-16T18:33:58.337Z"},"1.1.0":{"name":"event-iterator","version":"1.1.0","description":"Convert event emitters and event targets to ES async iterators","homepage":"https://github.com/rolftimmermans/event-iterator","license":"MIT","author":{"name":"Rolf Timmermans","email":"rolftimmermans@voormedia.com"},"repository":{"type":"git","url":"https://github.com/rolftimmermans/event-iterator.git"},"keywords":["async","async-iterator","event-emitter","event-target","stream","await","for-await","esnext","node","browser"],"main":"lib/node.js","browser":"lib/dom.js","devDependencies":{"@types/chai":">= 0","@types/jsdom":">= 0","@types/mocha":">= 0","@types/node":">= 8.0","chai":">= 4.1","jsdom":">= 11.0","mocha":">= 3.1","ts-node":">= 3.3","typescript":">= 3.3"},"scripts":{"test":"mocha && rm -rf lib && tsc"},"_id":"event-iterator@1.1.0","dist":{"shasum":"9cab6c8e58f213e759eaf5dc2cfc0c7567ad1565","size":7567,"noattachment":false,"key":"/event-iterator/-/event-iterator-1.1.0.tgz","tarball":"http://registry.cnpm.dingdandao.com/event-iterator/download/event-iterator-1.1.0.tgz"},"maintainers":[{"name":"rolftimmermans","email":""}],"_npmUser":{"name":"rolftimmermans","email":"r.w.timmermans@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/event-iterator_1.1.0_1553024218566_0.5392090623261983"},"_hasShrinkwrap":false,"publish_time":1553024218676,"_cnpm_publish_time":1553024218676,"_cnpmcore_publish_time":"2021-12-16T18:33:58.552Z"},"1.0.0":{"name":"event-iterator","version":"1.0.0","description":"Convert event emitters and event targets to ES async iterators","homepage":"https://github.com/rolftimmermans/event-iterator","license":"MIT","author":{"name":"Rolf Timmermans","email":"rolftimmermans@voormedia.com"},"repository":{"type":"git","url":"https://github.com/rolftimmermans/event-iterator.git"},"keywords":["async","async-iterator","event-emitter","event-target","stream","await","for-await","esnext","node","browser"],"main":"lib/node.js","browser":"lib/dom.js","devDependencies":{"@types/chai":">= 0","@types/jsdom":">= 0","@types/mocha":">= 0","@types/node":">= 8.0","chai":">= 4.1","jsdom":">= 11.0","mocha":">= 3.1","ts-node":">= 3.3","typescript":">= 2.5"},"scripts":{"test":"mocha && rm -rf lib && tsc"},"_id":"event-iterator@1.0.0","dist":{"shasum":"7511040a489e71751a704a7cfc68268865b27dff","size":43952,"noattachment":false,"key":"/event-iterator/-/event-iterator-1.0.0.tgz","tarball":"http://registry.cnpm.dingdandao.com/event-iterator/download/event-iterator-1.0.0.tgz"},"maintainers":[{"name":"rolftimmermans","email":""}],"_npmUser":{"name":"rolftimmermans","email":"r.w.timmermans@gmail.com"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/event-iterator-1.0.0.tgz_1506073905349_0.12994069838896394"},"directories":{},"publish_time":1506073906459,"_hasShrinkwrap":false,"_cnpm_publish_time":1506073906459,"_cnpmcore_publish_time":"2021-12-16T18:33:58.800Z"}},"readme":"# EventIterator: convert any JS event emitter to async iterators\n\n## Highlights\n\n`EventIterator` is a small module that greatly simplifies converting event\nemitters, event targets, and similar objects into EcmaScript async iterators. It\nworks in browser and Node.js environments.\n\nAs a bonus you get utility functions:\n\n  * `subscribe` to subscribe to events on a DOM event target with an async iterator\n  * `stream` to consume data from a Node.js readable stream as an async iterator\n\n## Basic examples\n\nFor client-side browser events:\n\n``` javascript\nimport \"core-js/es7/symbol\" /* If necessary */\nimport {subscribe} from \"event-iterator\"\nconst element = document.querySelector(\"a.example\")\n\nfor await (const click of subscribe.call(element, \"click\")) {\n  /* Asynchronously iterate over click events on the element. */\n}\n```\n\nFor server-side Node.js events:\n\n``` javascript\nimport \"core-js/es7/symbol\" /* If necessary */\nimport {stream} from \"event-iterator\"\nconst file = require(\"fs\").createReadStream(\"example-file\")\n\nfor await (const chunk of stream.call(file)) {\n  /* Asynchronously iterate over buffer chunks read from file. */\n}\n```\n\n## Advanced examples\n\nLet's look at how `subscribe()` and `stream()` are implemented.\n\nFor client-side browser events:\n\n``` javascript\nimport \"core-js/es7/symbol\" /* If necessary */\nimport {EventIterator} from \"event-iterator\"\n\nexport function subscribe(event, options) {\n  /* \"this\" refers to a DOM event target. */\n  return new EventIterator(\n    ({push}) => {\n      this.addEventListener(event, push, options)\n      return () => this.removeEventListener(event, push, options)\n    }\n  )\n}\n```\n\nFor server-side Node.js events:\n\n``` javascript\nimport \"core-js/es7/symbol\" /* If necessary */\nimport {EventIterator} from \"event-iterator\"\n\nexport function stream() {\n  /* \"this\" refers to a Node.js readable stream. */\n  return new EventIterator(\n    queue => {\n      this.addListener(\"data\", queue.push)\n      this.addListener(\"close\", queue.stop)\n      this.addListener(\"error\", queue.fail)\n\n      queue.on(\"highWater\", () => this.pause())\n      queue.on(\"lowWater\", () => this.resume())\n\n      return () => {\n        this.removeListener(\"data\", queue.push)\n        this.removeListener(\"close\", queue.stop)\n        this.removeListener(\"error\", queue.fail)\n        this.destroy()\n      }\n    }\n  )\n}\n```\n\n### Backpressure\n\nIf you cannot reasonably consume all emitted events with your async iterator;\nthe internal `EventIterator` queue can fill up indefinitely.\n\nA warning will be emitted when the queue reaches the high water mark (100 items\nby default).\n\nHowever, if you are able to control the event stream then you can listen to the\n`highWater`, and `lowWater` events to exert backpressure.\n\nWhen these events are emitted can be changed or disabled by setting\n`highWaterMark` and `lowWaterMark` in the options of the `EventIterator`\nconstructor.\n\n```js\nimport {EventIterator} from \"event-iterator\"\n\nconst eventIterator = new EventIterator(\n  ({push, on}) => {\n    const file = require(\"fs\").createReadStream(\"example-file\")\n    file.on(\"data\", push)\n    on(\"highWater\", () => file.pause())\n    on(\"lowWater\", () => file.resume())\n    return () => file.removeListener(\"data\", push)\n  },\n  {highWaterMark: 10, lowWaterMark: 5}\n)\n```\n\n\n## API specification\n\nCreate a new event iterator with `new EventIterator(listen)`. This\nobject implements the async iterator protocol by having a `Symbol.asyncIterator`\nproperty.\n\nNote: you must set up any `Symbol.asyncIterator` polyfills **before** importing\n`EventIterator`.\n\nThe `listen` handler is called every time a new iterator is created to set up\nyour event listeners. The optional `remove` handler is called when the event\nlisteners need to be removed. The `listen` handler returns the `remove`\nhandler, making it easy to call `addListener`/`removeListener` or similar\nfunctions.\n\nType definitions:\n\n``` typescript\nexport interface Queue<T> {\n  push(value: T): void\n  stop(): void\n  fail(error: Error): void\n  on(event: \"highWater\" | \"lowWater\", fn: () => void)\n}\n\nexport type RemoveHandler = () => void\nexport type ListenHandler<T> = (queue: Queue<T>) => void | RemoveHandler\n\n/* High water mark defaults to 100. Set to undefined to disable warnings. */\ninterface EventIteratorOptions = {\n  highWaterMark?: number,\n  lowWaterMark?: number,\n}\n\nclass EventIterator<T> {\n    constructor(ListenHandler<T>, options?: EventIteratorOptions)\n\n    [Symbol.asyncIterator](): AsyncIterator<T>\n}\n```\n\n## Background\n\nThe `EventIterator` class is an adapter to transform any browser or Node.js\nevent emitter into an async iterator that iterates over events.\n\nImagine you have a bunch of text files and in Node.js and you want to decide\nwhether they are longer or shorter than a certain number of lines. The files\nshould not be binary to avoid cluttering the results. To be more specific, we\nwant a function that will:\n\n  * return `true` if the number of lines is 1000 or greater\n  * return `false` if the number of lines is less than 1000\n  * throw an exception if the file appears to be binary (contains NULL bytes)\n  * returns as quickly as possible\n  * conserves memory by not reading entire files at once\n\nA naive solution would look like this:\n\n``` javascript\nfunction countLines(buffer) {\n  const str = buffer.toString()\n  if (str.match(\"\\0\")) throw new Error(\"Binary file!\")\n  return (str.match(/\\n/g) || []).length\n}\n\nfunction isLongTextFile(file) {\n  let lines = 1\n\n  return new Promise((resolve, reject) => {\n    file.on(\"data\", chunk => {\n      lines += countLines(chunk)\n    })\n\n    file.on(\"end\", () => {\n      resolve(lines >= 1000)\n    })\n\n    file.on(\"error\", err => {\n      reject(err)\n    })\n  })\n}\n\nisLongTextFile(fs.createReadStream(\"...\")).then(console.log)\n```\n\nUnfortunately, this solution has some problems:\n\n  * the entire file is read even if the file is way longer than 1000 lines\n  * the entire file is read even if an exception occurs (a NULL byte was found)\n  * multiple exceptions can be thrown if NULL bytes are found in multiple chunks\n\nSo we improve our solution, and we arrive at something like this:\n\n``` javascript\nfunction isLongTextFile(file) {\n  let lines = 1\n\n  const isLong = n => n >= 1000\n\n  return new Promise((resolve, reject) => {\n    file.on(\"data\", chunk => {\n      try {\n        lines += countLines(chunk)\n        if (isLong(lines)) {\n          file.close()\n          resolve(true)\n        }\n      } catch (err) {\n        file.destroy()\n        reject(err)\n      }\n    })\n\n    file.on(\"end\", () => {\n      resolve(isLong(lines))\n    })\n\n    file.on(\"error\", err => {\n      reject(err)\n    })\n  })\n}\n\nisLongTextFile(fs.createReadStream(\"...\")).then(console.log)\n```\n\nThis works and we're happy to have solved the problem!\n\nBut what if there were a nicer way to do this? Async iterators sure seem like a\nnice fit for this problem. They are a stage 3 EcmaScript proposal and can be\nused by using TypeScript or Babel.\n\nA similar solution using async iterators could look like this:\n\n``` javascript\nfunction async isLongTextFile(file) {\n  let lines = 1\n  for await (const chunk of stream.call(file)) { // or file::stream()\n    lines += countLines(chunk)\n    if (lines > 1000) return true\n  }\n  return false\n}\n\nisLongTextFile(fs.createReadStream(\"...\")).then(console.log)\n```\n\nThe question is: how do you create an async iterator from a readable stream?\nConceptually they are very similar; they both:\n\n  * can signal that a next value is available\n  * can signal when the end has been reached\n  * can emit an error, after which no new values will become available\n\nAsync iterators have a few additional advantages that translate in simpler code:\n\n  * early returns and exceptions will stop the iterator and release resources\n  * exceptions in the iterator and in calling code can be handled without additional boilerplate\n\nSo how do you transform a readble stream into an async iterator? With an `EventIterator`.\n\nWe can define the `stream` function above as:\n\n``` javascript\nimport {EventIterator} from \"event-iterator\"\n\nfunction stream() {\n  return new EventIterator(\n    ({ push, stop, fail }) => {\n      this.addListener(\"data\", push)\n      this.addListener(\"end\", stop)\n      this.addListener(\"error\", fail)\n\n      return () => {\n        this.removeListener(\"data\", push)\n        this.removeListener(\"end\", stop)\n        this.removeListener(\"error\", fail)\n        this.destroy()\n      }\n    }\n  )\n}\n```\n\nThe `EventIterator` takes care of:\n\n  * conforming to the async iterator spec\n  * returning placeholder promises to the async iterator if no value is available\n  * queueing values when more data is ready than consumed by the iterator\n  * installing callbacks to release resources so you can remove any listener handlers or do other cleanup\n\nWhy create an abstract `EventIterator` that requires you to define your own\nintegration code? Several reasons:\n\n  * the event emitters as defined by Node.js have a different API than the event targets as defined in the DOM\n  * the events that you are interested in may have different names depending on your use case\n  * you may want to specify custom behaviour when the iterator throws or returns early\n\n## Licensed under MIT license\n\nCopyright (c) 2017-2020 Rolf Timmermans\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n","_attachments":{},"homepage":"https://github.com/rolftimmermans/event-iterator","bugs":{"url":"https://github.com/rolftimmermans/event-iterator/issues"},"license":"MIT"}