From a7c518171a70ba81224735117856db639ff1cd6b Mon Sep 17 00:00:00 2001 From: hunghg255 Date: Tue, 16 Jul 2024 00:58:08 +0700 Subject: [PATCH] feat: add pChain --- .github/workflows/deploy-docs.yml | 5 +- docs/.vitepress/locale.ts | 5 +- docs/package.json | 2 +- docs/reference/promise/pChain.md | 51 ++++++++++++++++ src/promise/pChain.spec.ts | 19 ++++++ src/promise/pChain.ts | 96 +++++++++++++++++++++++++++++++ 6 files changed, 174 insertions(+), 4 deletions(-) create mode 100644 docs/reference/promise/pChain.md create mode 100644 src/promise/pChain.spec.ts create mode 100644 src/promise/pChain.ts diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index c337f11..e0cee62 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -3,10 +3,11 @@ name: Vercel Deployment Development env: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + on: push: - branches: - - main + tags: + - 'v*' # This is what will cancel the workflow concurrency: diff --git a/docs/.vitepress/locale.ts b/docs/.vitepress/locale.ts index 6b66ffa..35ce040 100644 --- a/docs/.vitepress/locale.ts +++ b/docs/.vitepress/locale.ts @@ -158,7 +158,10 @@ export function getLocaleConfig(lang: string) { }, { text: t('Promise Utilities'), - items: [{ text: 'delay', link: '/reference/promise/delay' }], + items: [ + { text: 'delay', link: '/reference/promise/delay' }, + { text: 'pChain', link: '/reference/promise/pChain' }, + ], }, { text: t('String Utilities'), diff --git a/docs/package.json b/docs/package.json index e15c4ea..ab44586 100644 --- a/docs/package.json +++ b/docs/package.json @@ -20,7 +20,7 @@ "@nolebase/vitepress-plugin-highlight-targeted-heading": "^2.2.1", "@shikijs/vitepress-twoslash": "^1.10.3", "@vitejs/plugin-vue-jsx": "^4.0.0", - "js-utils-es": "^1.0.5", + "js-utils-es": "^1.0.6", "unocss": "^0.61.3", "vite": "^5.3.3", "vitepress": "^1.3.0", diff --git a/docs/reference/promise/pChain.md b/docs/reference/promise/pChain.md new file mode 100644 index 0000000..b84b64d --- /dev/null +++ b/docs/reference/promise/pChain.md @@ -0,0 +1,51 @@ +# pChain + +Utility for managing multiple promises. + +## Signature + +```typescript +function pChain(items?: Iterable): PInstance; +``` + +### Parameters + +- `items` (`Iterable`): An optional iterable of items to chain. + +### Returns + +(`PInstance`): A `PInstance` object that can be used to chain promises. + +## Description + +```ts +declare class PInstance extends Promise[]> { + items: Iterable; + private promises; + get promise(): Promise[]>; + constructor(items?: Iterable); + add(...args: (T | Promise)[]): void; + map(fn: (value: Awaited, index: number) => U): PInstance>; + filter(fn: (value: Awaited, index: number) => boolean | Promise): PInstance>; + forEach(fn: (value: Awaited, index: number) => void): Promise; + reduce(fn: (previousValue: U, currentValue: Awaited, currentIndex: number, array: Awaited[]) => U, initialValue: U): Promise; + clear(): void; + then(fn?: () => PromiseLike): Promise; + catch(fn?: (err: unknown) => PromiseLike): Promise; + finally(fn?: () => void): Promise[]>; +} +``` + +## Examples + +```typescript +import { pChain } from 'js-utils-es/promise'; + +const items = [1, 2, 3, 4, 5]; + +await pChain(items) + .map(async i => await multiply(i, 3)) + .filter(async i => await isEven(i)) + +// Result: [6, 12] +``` diff --git a/src/promise/pChain.spec.ts b/src/promise/pChain.spec.ts new file mode 100644 index 0000000..f8b5b47 --- /dev/null +++ b/src/promise/pChain.spec.ts @@ -0,0 +1,19 @@ +import { describe, expect, it } from 'vitest' +import { pChain } from './pChain' + +const timeout = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) + +describe('pChain', () => { + it('chain array map', async () => { + expect( + await pChain([1, 2, 3, 4, 5]) + .map(async (i) => { + await timeout(10) + return i * i + }) + .filter(i => i > 10) + .reduce((a, b) => a + b, 0), + ) + .toEqual(41) + }) +}) diff --git a/src/promise/pChain.ts b/src/promise/pChain.ts new file mode 100644 index 0000000..5fdd3e2 --- /dev/null +++ b/src/promise/pChain.ts @@ -0,0 +1,96 @@ +/** + * Internal marker for filtered items + */ +const VOID = Symbol('p-void') + +class PInstance extends Promise[]> { + private promises = new Set>() + + get promise(): Promise[]> { + const items = [...Array.from(this.items), ...Array.from(this.promises)] + + const batch = Promise.all(items) + + return batch.then(l => l.filter((i: any) => i !== VOID)) + } + + constructor(public items: Iterable = []) { + super(() => {}) + } + + add(...args: (T | Promise)[]) { + args.forEach((i) => { + this.promises.add(i) + }) + } + + map(fn: (value: Awaited, index: number) => U): PInstance> { + return new PInstance( + Array.from(this.items) + .map(async (i, idx) => { + const v = await i + if ((v as any) === VOID) + return VOID as unknown as U + return fn(v, idx) + }), + ) + } + + filter(fn: (value: Awaited, index: number) => boolean | Promise): PInstance> { + return new PInstance( + Array.from(this.items) + .map(async (i, idx) => { + const v = await i + const r = await fn(v, idx) + if (!r) + return VOID as unknown as T + return v + }), + ) + } + + forEach(fn: (value: Awaited, index: number) => void): Promise { + return this.map(fn).then() + } + + reduce(fn: (previousValue: U, currentValue: Awaited, currentIndex: number, array: Awaited[]) => U, initialValue: U): Promise { + return this.promise.then(array => array.reduce(fn, initialValue)) + } + + clear() { + this.promises.clear() + } + + then(fn?: () => PromiseLike) { + const p = this.promise + if (fn) + return p.then(fn) + else + return p + } + + catch(fn?: (err: unknown) => PromiseLike) { + return this.promise.catch(fn) + } + + finally(fn?: () => void) { + return this.promise.finally(fn) + } +} + +/** + * Utility for managing multiple promises. + * + * @example + * ```js + * const items = [1, 2, 3, 4, 5] + * + * await p(items) + * .map(async i => await multiply(i, 3)) + * .filter(async i => await isEven(i)) + * // [6, 12] + * ``` + */ +export function pChain(items?: Iterable): PInstance { + return new PInstance(items) +}