Skip to content

Commit

Permalink
feat: support for function-generator
Browse files Browse the repository at this point in the history
  • Loading branch information
TanyaLagodich committed Sep 2, 2024
1 parent 629b829 commit f8bddd8
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 15 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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.**
Expand Down
30 changes: 30 additions & 0 deletions README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Iter8or — это универсальная библиотека для раб
## Особенности

- **Поддержка синхронных и асинхронных итераторов**: Обрабатывайте как синхронные, так и асинхронные данные с помощью единого API.
- **Поддержка функций-генераторов**: Поддержка как синхронных, так и асинхронных функций-генераторов. Функция будет вызвана автоматически, и возвращаемый генератор будет использоваться для итерации.
- **Создание итераторов из неитерируемых объектов**:
- Для чисел (`number`):
- По умолчанию создается итератор, который проходит от _1_ (или _-1_, если передано число меньше 0) до указанного числа включительно (диапазон).
Expand Down Expand Up @@ -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`. **В противном случае итератор будет обрабатываться как синхронный.** Например:
Expand Down
95 changes: 82 additions & 13 deletions src/core/Iter8or.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
}
Expand All @@ -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
);
}

Expand Down Expand Up @@ -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
);
}

Expand Down
64 changes: 64 additions & 0 deletions src/core/__tests__/Iter8or.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
});
});
1 change: 0 additions & 1 deletion src/core/docs/iter8or.jsdoc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
* @throws {TypeError} If `iterable` is null, undefined, boolean, or function.
*/


/**
* Returns the next value in the iteration.
* @method next
Expand Down
1 change: 0 additions & 1 deletion src/modifiers/filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit f8bddd8

Please sign in to comment.