From 1dec7350461c81c79b5270b909e7d505620646b2 Mon Sep 17 00:00:00 2001 From: William Wong Date: Thu, 13 Jun 2024 02:39:42 -0700 Subject: [PATCH] Add `asyncIteratorToAsyncIterable` (#19) * Add `asyncIteratorToAsyncIterable` * Typo --- CHANGELOG.md | 1 + README.md | 3 +- .../integration-test/importDefault.test.ts | 21 ++++++++++++++ packages/integration-test/importNamed.test.ts | 21 ++++++++++++++ .../integration-test/requireNamed.test.cjs | 21 ++++++++++++++ .../integration-test/requiredDefault.test.cjs | 21 ++++++++++++++ packages/iter-fest/package.json | 10 +++++++ .../src/asyncIteratorToAsyncIterable.spec.ts | 28 +++++++++++++++++++ .../src/asyncIteratorToAsyncIterable.ts | 10 +++++++ packages/iter-fest/src/index.ts | 1 + packages/iter-fest/tsup.config.ts | 1 + 11 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 packages/iter-fest/src/asyncIteratorToAsyncIterable.spec.ts create mode 100644 packages/iter-fest/src/asyncIteratorToAsyncIterable.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index f153ceb..af6adb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `observableSubscribeAsReadable` in PR [#13](https://github.com/compulim/iter-fest/pull/13) - Added `iterableGetReadable` in PR [#15](https://github.com/compulim/iter-fest/pull/15) - Added `generatorWithLastValue`/`asyncGeneratorWithLastValue` in PR [#17](https://github.com/compulim/iter-fest/pull/17) +- Added `asyncIteratorToAsyncIterable` in PR [#18](https://github.com/compulim/iter-fest/pull/18) ### Changed diff --git a/README.md b/README.md index b4f6af2..758d46a 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ List of ported functions: [`at`](https://tc39.es/ecma262/#sec-array.prototype.at | From | To | Function signature | | ----------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | `Iterator` | `IterableIterator` | [`iteratorToIterable(iterator: Iterator): IterableIterator`](#converting-an-iterator-to-iterable) | +| `AsyncIterator` | `AsyncIterableIterator` | [`asyncIteratorToAsyncIterable(asyncIterator: AsyncIterator): AsyncIterableIterator`](#converting-an-iterator-to-iterable) | | `Observable` | `ReadableStream` | [`observableSubscribeAsReadable(observable: Observable): ReadableStream`](#converting-an-observable-to-readablestream) | | `ReadableStreamDefaultReader` | `AsyncIterableIterator` | [`readerValues`(reader: ReadableStreamDefaultReader): AsyncIterableIterator`](#iterating-readablestreamdefaultreader) | | `AsyncIterable` | `Observable` | [`observableFromAsync(iterable: AsyncIterable): Observable`](#converting-an-asynciterable-to-observable) | @@ -47,7 +48,7 @@ To convert `Observable` to `AsyncIterableIterator`, [use `ReadableStream` as int ### Converting an iterator to iterable -`iteratorToIterable` enable a [pure iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator) to be iterable using a for-loop statement. +`iteratorToIterable` and `asyncIteratorToAsyncIterable` enable a [pure iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator) to be iterable using a for-loop statement. ```ts const iterate = (): Iterator => { diff --git a/packages/integration-test/importDefault.test.ts b/packages/integration-test/importDefault.test.ts index a7d71b5..fe0ac3f 100644 --- a/packages/integration-test/importDefault.test.ts +++ b/packages/integration-test/importDefault.test.ts @@ -4,6 +4,7 @@ import { PushAsyncIterableIterator, SymbolObservable, asyncGeneratorWithLastValue, + asyncIteratorToAsyncIterable, generatorWithLastValue, iterableAt, iterableConcat, @@ -32,6 +33,26 @@ import { readerValues } from 'iter-fest'; +test('asyncIteratorToIterable should work', async () => { + const iterable = asyncIteratorToAsyncIterable( + ((): AsyncIterator => { + let value = 0; + + return { + next: () => Promise.resolve(++value <= 3 ? { done: false, value } : { done: true, value: undefined }) + }; + })() + ); + + const values: number[] = []; + + for await (const value of iterable) { + values.push(value); + } + + expect(values).toEqual([1, 2, 3]); +}); + test('asyncGeneratorWithLastValue should work', async () => { const asyncGenerator = asyncGeneratorWithLastValue( (async function* () { diff --git a/packages/integration-test/importNamed.test.ts b/packages/integration-test/importNamed.test.ts index daa640a..920be36 100644 --- a/packages/integration-test/importNamed.test.ts +++ b/packages/integration-test/importNamed.test.ts @@ -1,5 +1,6 @@ import withResolvers from 'core-js-pure/full/promise/with-resolvers'; import { asyncGeneratorWithLastValue } from 'iter-fest/asyncGeneratorWithLastValue'; +import { asyncIteratorToAsyncIterable } from 'iter-fest/asyncIteratorToAsyncIterable'; import { generatorWithLastValue } from 'iter-fest/generatorWithLastValue'; import { iterableAt } from 'iter-fest/iterableAt'; import { iterableConcat } from 'iter-fest/iterableConcat'; @@ -46,6 +47,26 @@ test('asyncGeneratorWithLastValue should work', async () => { expect(asyncGenerator.lastValue()).toEqual('end'); }); +test('asyncIteratorToIterable should work', async () => { + const iterable = asyncIteratorToAsyncIterable( + ((): AsyncIterator => { + let value = 0; + + return { + next: () => Promise.resolve(++value <= 3 ? { done: false, value } : { done: true, value: undefined }) + }; + })() + ); + + const values: number[] = []; + + for await (const value of iterable) { + values.push(value); + } + + expect(values).toEqual([1, 2, 3]); +}); + test('generatorWithLastValue should work', () => { const generator = generatorWithLastValue( (function* () { diff --git a/packages/integration-test/requireNamed.test.cjs b/packages/integration-test/requireNamed.test.cjs index 317262e..4f0f423 100644 --- a/packages/integration-test/requireNamed.test.cjs +++ b/packages/integration-test/requireNamed.test.cjs @@ -1,6 +1,7 @@ const withResolvers = require('core-js-pure/full/promise/with-resolvers'); const { asyncGeneratorWithLastValue } = require('iter-fest/asyncGeneratorWithLastValue'); +const { asyncIteratorToAsyncIterable } = require('iter-fest/asyncIteratorToAsyncIterable'); const { generatorWithLastValue } = require('iter-fest/generatorWithLastValue'); const { iterableAt } = require('iter-fest/iterableAt'); const { iterableConcat } = require('iter-fest/iterableConcat'); @@ -47,6 +48,26 @@ test('asyncGeneratorWithLastValue should work', async () => { expect(asyncGenerator.lastValue()).toEqual('end'); }); +test('asyncIteratorToIterable should work', async () => { + const iterable = asyncIteratorToAsyncIterable( + (() => { + let value = 0; + + return { + next: () => Promise.resolve(++value <= 3 ? { done: false, value } : { done: true, value: undefined }) + }; + })() + ); + + const values = []; + + for await (const value of iterable) { + values.push(value); + } + + expect(values).toEqual([1, 2, 3]); +}); + test('generatorWithLastValue should work', () => { const generator = generatorWithLastValue( (function* () { diff --git a/packages/integration-test/requiredDefault.test.cjs b/packages/integration-test/requiredDefault.test.cjs index 56acb62..da288e1 100644 --- a/packages/integration-test/requiredDefault.test.cjs +++ b/packages/integration-test/requiredDefault.test.cjs @@ -2,6 +2,7 @@ const withResolvers = require('core-js-pure/full/promise/with-resolvers'); const { asyncGeneratorWithLastValue, + asyncIteratorToAsyncIterable, generatorWithLastValue, iterableAt, iterableConcat, @@ -49,6 +50,26 @@ test('asyncGeneratorWithLastValue should work', async () => { expect(asyncGenerator.lastValue()).toEqual('end'); }); +test('asyncIteratorToIterable should work', async () => { + const iterable = asyncIteratorToAsyncIterable( + (() => { + let value = 0; + + return { + next: () => Promise.resolve(++value <= 3 ? { done: false, value } : { done: true, value: undefined }) + }; + })() + ); + + const values = []; + + for await (const value of iterable) { + values.push(value); + } + + expect(values).toEqual([1, 2, 3]); +}); + test('generatorWithLastValue should work', () => { const generator = generatorWithLastValue( (function* () { diff --git a/packages/iter-fest/package.json b/packages/iter-fest/package.json index a39e8f0..74fe4c5 100644 --- a/packages/iter-fest/package.json +++ b/packages/iter-fest/package.json @@ -16,6 +16,16 @@ "default": "./dist/iter-fest.asyncGeneratorWithLastValue.js" } }, + "./asyncIteratorToAsyncIterable": { + "import": { + "types": "./dist/iter-fest.asyncIteratorToAsyncIterable.d.mts", + "default": "./dist/iter-fest.asyncIteratorToAsyncIterable.mjs" + }, + "require": { + "types": "./dist/iter-fest.asyncIteratorToAsyncIterable.d.ts", + "default": "./dist/iter-fest.asyncIteratorToAsyncIterable.js" + } + }, "./generatorWithLastValue": { "import": { "types": "./dist/iter-fest.generatorWithLastValue.d.mts", diff --git a/packages/iter-fest/src/asyncIteratorToAsyncIterable.spec.ts b/packages/iter-fest/src/asyncIteratorToAsyncIterable.spec.ts new file mode 100644 index 0000000..6825829 --- /dev/null +++ b/packages/iter-fest/src/asyncIteratorToAsyncIterable.spec.ts @@ -0,0 +1,28 @@ +import { asyncIteratorToAsyncIterable } from './asyncIteratorToAsyncIterable'; + +describe('passing an async iterator-compatible generator', () => { + let asyncIterable: AsyncIterableIterator; + + beforeEach(() => { + const iterate = (): AsyncIterator => { + let value = 0; + + return { + next: (): Promise> => + Promise.resolve(++value <= 3 ? { done: false, value } : { done: true, value: undefined }) + }; + }; + + asyncIterable = asyncIteratorToAsyncIterable(iterate()); + }); + + test('should be iterable', async () => { + const values: number[] = []; + + for await (const value of asyncIterable) { + values.push(value); + } + + expect(values).toEqual([1, 2, 3]); + }); +}); diff --git a/packages/iter-fest/src/asyncIteratorToAsyncIterable.ts b/packages/iter-fest/src/asyncIteratorToAsyncIterable.ts new file mode 100644 index 0000000..a97d4ec --- /dev/null +++ b/packages/iter-fest/src/asyncIteratorToAsyncIterable.ts @@ -0,0 +1,10 @@ +export function asyncIteratorToAsyncIterable(asyncIterator: AsyncIterator): AsyncIterableIterator { + const asyncIterableIterator: AsyncIterableIterator = { + [Symbol.asyncIterator]: () => asyncIterableIterator, + next: asyncIterator.next.bind(asyncIterator), + ...(asyncIterator.return ? { return: asyncIterator.return.bind(asyncIterator) } : {}), + ...(asyncIterator.throw ? { throw: asyncIterator.throw.bind(asyncIterator) } : {}) + }; + + return asyncIterableIterator; +} diff --git a/packages/iter-fest/src/index.ts b/packages/iter-fest/src/index.ts index 8a02c3a..9db3c26 100644 --- a/packages/iter-fest/src/index.ts +++ b/packages/iter-fest/src/index.ts @@ -2,6 +2,7 @@ export * from './Observable'; export * from './PushAsyncIterableIterator'; export * from './SymbolObservable'; export * from './asyncGeneratorWithLastValue'; +export * from './asyncIteratorToAsyncIterable'; export * from './generatorWithLastValue'; export * from './iterableAt'; export * from './iterableConcat'; diff --git a/packages/iter-fest/tsup.config.ts b/packages/iter-fest/tsup.config.ts index f169bcd..2aa5fa4 100644 --- a/packages/iter-fest/tsup.config.ts +++ b/packages/iter-fest/tsup.config.ts @@ -6,6 +6,7 @@ export default defineConfig([ entry: { 'iter-fest': './src/index.ts', 'iter-fest.asyncGeneratorWithLastValue': './src/asyncGeneratorWithLastValue.ts', + 'iter-fest.asyncIteratorToAsyncIterable': './src/asyncIteratorToAsyncIterable.ts', 'iter-fest.generatorWithLastValue': './src/generatorWithLastValue.ts', 'iter-fest.iterableAt': './src/iterableAt.ts', 'iter-fest.iterableConcat': './src/iterableConcat.ts',