From f8bddd883fed82973b7452bd5821cc724d7338c4 Mon Sep 17 00:00:00 2001 From: Tatiana Lagodich Date: Mon, 2 Sep 2024 11:22:30 +0400 Subject: [PATCH] feat: support for function-generator --- README.md | 29 +++++++++ README.ru.md | 30 ++++++++++ src/core/Iter8or.js | 95 ++++++++++++++++++++++++++---- src/core/__tests__/Iter8or.spec.js | 64 ++++++++++++++++++++ src/core/docs/iter8or.jsdoc.js | 1 - src/modifiers/filter.js | 1 - 6 files changed, 205 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 764149c..a817ee1 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Iter8or is a versatile library for working with iterators and asynchronous itera ## Features - **Support for both synchronous and asynchronous iterators**: Process both synchronous and asynchronous data using a single API. +- **Support for generator functions**: You can pass generator functions or async generator functions to the `Iter8or` constructor. The function will be invoked automatically, and the returned iterator will be used. - **Creation of iterators from non-iterable objects:**: - For numbers (`number`): - **By default**, an iterator is created that iterates from _1_ (or _-1_ if the number is negative) to the specified number inclusively (range). @@ -56,6 +57,34 @@ for (const [key, value] of objIter) { // b 2 ``` +- ### With a generator function +```javascript +function* generatorFunction() { + yield 1; + yield 2; + yield 3; +} + +const iter = new Iter8or(generatorFunction); +console.log([...iter]); // [1, 2, 3] +``` + +- ### With an async generator function +```javascript +async function* asyncGeneratorFunction() { + yield 1; + yield 2; + yield 3; +} + +const iter = new Iter8or(asyncGeneratorFunction, { async: true }); +(async () => { + for await (const value of iter) { + console.log(value); // 1, 2, 3 + } +})(); +``` + ## Working with Asynchronous Iterators To use asynchronous iterators, you need to pass the `{ async: true }` option to the constructor. This allows you to correctly process asynchronous data using `await`. **Otherwise, the iterator will be processed as a synchronous iterator.** diff --git a/README.ru.md b/README.ru.md index 83301b8..f5a9894 100644 --- a/README.ru.md +++ b/README.ru.md @@ -8,6 +8,7 @@ Iter8or — это универсальная библиотека для раб ## Особенности - **Поддержка синхронных и асинхронных итераторов**: Обрабатывайте как синхронные, так и асинхронные данные с помощью единого API. +- **Поддержка функций-генераторов**: Поддержка как синхронных, так и асинхронных функций-генераторов. Функция будет вызвана автоматически, и возвращаемый генератор будет использоваться для итерации. - **Создание итераторов из неитерируемых объектов**: - Для чисел (`number`): - По умолчанию создается итератор, который проходит от _1_ (или _-1_, если передано число меньше 0) до указанного числа включительно (диапазон). @@ -55,6 +56,35 @@ for (const [key, value] of objIter) { // b 2 ``` +- ### с функциями-генераторами +```javascript +function* generatorFunction() { + yield 1; + yield 2; + yield 3; +} + +const iter = new Iter8or(generatorFunction); +console.log([...iter]); // [1, 2, 3] +``` + +- ### с асинхронными генераторами +```javascript +async function* asyncGeneratorFunction() { + yield 1; + yield 2; + yield 3; +} + +const iter = new Iter8or(asyncGeneratorFunction, { async: true }); +(async () => { + for await (const value of iter) { + console.log(value); // 1, 2, 3 + } +})(); +``` + + ## Работа с асинхронными итераторами Чтобы использовать асинхронные итераторы, необходимо передать опцию `{ async: true }` в конструктор. Это позволит корректно обработать асинхронные данные с использованием `await`. **В противном случае итератор будет обрабатываться как синхронный.** Например: diff --git a/src/core/Iter8or.js b/src/core/Iter8or.js index 4774377..8982440 100644 --- a/src/core/Iter8or.js +++ b/src/core/Iter8or.js @@ -34,7 +34,14 @@ export default class Iter8or { } if (typeof iterable === 'function') { - throw new TypeError('Cannot create an iterable from a function.'); + iterable = iterable(); + + if ( + !iterable || + (!iterable[Symbol.iterator] && !iterable[Symbol.asyncIterator]) + ) { + throw new TypeError('Cannot create an iterable from a function.'); + } } this.options = options; @@ -53,26 +60,88 @@ export default class Iter8or { [Symbol.iterator]() { if (this.options.async) { throw new TypeError( - 'This is a async iterable. Use Symbol.asyncIterator instead.' + 'This is a async iterable. Use Symbol.asyncIterator instead.' ); } else { - return this.iterable[Symbol.iterator](); + const iterator = this.iterable[Symbol.iterator](); + const stack = [iterator]; + + return { + next() { + while (stack.length > 0) { + const currentIterator = stack[stack.length - 1]; + const { done, value } = currentIterator.next(); + + if (done) { + stack.pop(); + continue; + } + + if ( + value && + (typeof value[Symbol.iterator] === 'function' || + typeof value[Symbol.asyncIterator] === 'function') + ) { + stack.push(value); + continue; + } + + return { done: false, value }; + } + + return { done: true, value: undefined }; + }, + [Symbol.iterator]() { + return this; + }, + }; } } [Symbol.asyncIterator]() { if (!this.options.async) { throw new TypeError( - 'This is a sync iterable. Use Symbol.iterator instead.' + 'This is a sync iterable. Use Symbol.iterator instead.' ); } - return this.iterable[Symbol.asyncIterator](); + const iterator = this.iterable[Symbol.asyncIterator](); + const stack = [iterator]; // Стек для хранения текущих итераторов + return { + async next() { + while (stack.length > 0) { + const currentIterator = stack[stack.length - 1]; + const { done, value } = await currentIterator.next(); + + if (done) { + stack.pop(); + continue; + } + + if ( + (value && typeof value[Symbol.asyncIterator] === 'function') || + typeof value[Symbol.iterator] === 'function' + ) { + stack.push(value); + continue; + } + + return { done: false, value }; + } + + return { done: true, value: undefined }; + }, + [Symbol.asyncIterator]() { + return this; + }, + }; } next() { if (!this.iterator) { - this.iterator = this.options.async ? this[Symbol.asyncIterator]() : this[Symbol.iterator](); + this.iterator = this.options.async + ? this[Symbol.asyncIterator]() + : this[Symbol.iterator](); } return this.iterator.next(); } @@ -97,8 +166,8 @@ export default class Iter8or { filter(predicate) { return new Iter8or( - createFilterIterator(this.iterable, predicate), - this.options + createFilterIterator(this.iterable, predicate), + this.options ); } @@ -127,11 +196,11 @@ export default class Iter8or { throw new Error('All concatted iterators must be an Iter8or.'); } return new Iter8or( - createConcatIterator( - this.iterable, - ...iterators.map((iterator) => iterator.iterable) - ), - this.options + createConcatIterator( + this.iterable, + ...iterators.map((iterator) => iterator.iterable) + ), + this.options ); } diff --git a/src/core/__tests__/Iter8or.spec.js b/src/core/__tests__/Iter8or.spec.js index f614891..3249fe5 100644 --- a/src/core/__tests__/Iter8or.spec.js +++ b/src/core/__tests__/Iter8or.spec.js @@ -118,4 +118,68 @@ describe('Iter8or class', () => { const str = iter.toString(); expect(str).toBe('Hello'); }); + + it('should handle generator objects correctly', () => { + function* generator() { + yield 1; + yield 2; + yield 3; + } + + const iter = new Iter8or(generator()); + expect([...iter]).toEqual([1, 2, 3]); + }); + + it('should handle generator functions correctly by invoking them', () => { + function* generatorFunction() { + yield 1; + yield 2; + yield 3; + } + + const iter = new Iter8or(generatorFunction); + expect([...iter]).toEqual([1, 2, 3]); + }); + + it('should throw an error if a function does not return an iterable', () => { + const nonIterableFunction = () => 123; + + expect(() => { + new Iter8or(nonIterableFunction); + }).toThrow(TypeError); + }); + + it('should handle functions returning iterables', () => { + const iter = new Iter8or(() => [1, 2, 3]); + expect([...iter]).toEqual([1, 2, 3]); + }); + + it('should handle functions returning async iterables', async () => { + const asyncFunc = async function* () { + yield 1; + yield 2; + yield 3; + }; + const iter = new Iter8or(asyncFunc(), { async: true }); + const result = []; + for await (const value of iter) { + result.push(value); + } + expect(result).toEqual([1, 2, 3]); + }); + + it('should handle async generator objects correctly', async () => { + async function* asyncGenerator() { + yield 1; + yield 2; + yield 3; + } + + const iter = new Iter8or(asyncGenerator(), { async: true }); + const result = []; + for await (const value of iter) { + result.push(value); + } + expect(result).toEqual([1, 2, 3]); + }); }); diff --git a/src/core/docs/iter8or.jsdoc.js b/src/core/docs/iter8or.jsdoc.js index d2930b2..e3445cb 100644 --- a/src/core/docs/iter8or.jsdoc.js +++ b/src/core/docs/iter8or.jsdoc.js @@ -10,7 +10,6 @@ * @throws {TypeError} If `iterable` is null, undefined, boolean, or function. */ - /** * Returns the next value in the iteration. * @method next diff --git a/src/modifiers/filter.js b/src/modifiers/filter.js index 84afe62..3d98310 100644 --- a/src/modifiers/filter.js +++ b/src/modifiers/filter.js @@ -51,7 +51,6 @@ const createAsyncFilterIterator = (iterable, predicate) => { }; export default function createFilterIterator(iterable, predicate) { - console.log(typeof iterable[Symbol.asyncIterator] === 'function') return typeof iterable[Symbol.asyncIterator] === 'function' ? createAsyncFilterIterator(iterable, predicate) : createSyncFilterIterator(iterable, predicate);