From a0a3c0eea971b004c85c602a025efe1f04ff1b5e Mon Sep 17 00:00:00 2001 From: Dan MacTough Date: Mon, 23 Mar 2026 19:41:12 -0400 Subject: [PATCH] Add async iterator support via Symbol.asyncIterator FeedParser instances can now be consumed directly with for await...of. The implementation bridges the push-based stream event model to the pull-based async iteration protocol using an async generator, without changing the underlying readable-stream v2 implementation. Co-Authored-By: Claude Sonnet 4.6 --- .eslintrc.json | 6 +++++- index.d.ts | 1 + lib/feedparser/index.js | 41 +++++++++++++++++++++++++++++++++++++++++ test/examples.js | 17 +++++++++++++++++ 4 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 test/examples.js diff --git a/.eslintrc.json b/.eslintrc.json index 43cc6da..8761f2d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,6 +1,10 @@ { "env": { - "node": true + "node": true, + "es6": true + }, + "parserOptions": { + "ecmaVersion": 2018 }, "extends": "eslint:recommended", "rules": { diff --git a/index.d.ts b/index.d.ts index 6e581af..abad0ae 100644 --- a/index.d.ts +++ b/index.d.ts @@ -10,6 +10,7 @@ declare class FeedParser extends stream.Transform { options: FeedParser.Options; read(): FeedParser.Item | null; + [Symbol.asyncIterator](): AsyncGenerator; resumeSaxError(): void; on(event: 'meta', listener: (meta: FeedParser.Meta) => void): this; diff --git a/lib/feedparser/index.js b/lib/feedparser/index.js index da2861c..a1beeaf 100644 --- a/lib/feedparser/index.js +++ b/lib/feedparser/index.js @@ -1237,4 +1237,45 @@ FeedParser.prototype._flush = function (done) { * @typedef {import('readable-stream').Transform & FeedParserState} FeedParserInstance */ +/** @this {FeedParserInstance} */ +FeedParser.prototype[Symbol.asyncIterator] = async function* () { + var resolve = null; + var error = null; + var ended = false; + + function onReadable() { + if (resolve) { resolve(); resolve = null; } + } + function onEnd() { + ended = true; + if (resolve) { resolve(); resolve = null; } + } + function onError(err) { + error = err; + if (resolve) { resolve(); resolve = null; } + } + + this.on('readable', onReadable); + this.on('end', onEnd); + this.on('error', onError); + + try { + while (true) { + var item; + while ((item = this.read()) !== null) { + yield item; + } + if (ended) break; + if (error) throw error; + await new Promise(function (r) { resolve = r; }); + resolve = null; + if (error) throw error; + } + } finally { + this.removeListener('readable', onReadable); + this.removeListener('end', onEnd); + this.removeListener('error', onError); + } +}; + exports = module.exports = FeedParser; diff --git a/test/examples.js b/test/examples.js new file mode 100644 index 0000000..b1f2496 --- /dev/null +++ b/test/examples.js @@ -0,0 +1,17 @@ +var FeedParser = require('../'); + +describe('examples', function () { + it('should work as an async iterator', async function () { + var feedparser = new FeedParser(); + var feed = __dirname + '/feeds/rss2sample.xml'; + var items = []; + + fs.createReadStream(feed).pipe(feedparser); + + for await (const item of feedparser) { + items.push(item); + } + + assert.equal(items.length, 4); + }); +});