Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions website/docs/.vitepress/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const enConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
},
{ text: "Lazy Evaluation", link: "/guide/lazy-evaluation" },
{ text: "Handle Concurrency", link: "/guide/handle-concurrency" },
{ text: "Migrate from p-limit/p-map", link: "/guide/migrate-from-p-map" },
{ text: "Error Handling", link: "/guide/error-handling" },
{ text: "Method Chaining", link: "/guide/method-chaining" },
],
Expand Down
1 change: 1 addition & 0 deletions website/docs/.vitepress/locales/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const jaConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
},
{ text: "遅延評価", link: "/ja/guide/lazy-evaluation" },
{ text: "並行処理", link: "/ja/guide/handle-concurrency" },
{ text: "p-limit/p-mapからの移行", link: "/ja/guide/migrate-from-p-map" },
{ text: "エラー処理", link: "/ja/guide/error-handling" },
{ text: "メソッドチェーン", link: "/ja/guide/method-chaining" },
],
Expand Down
1 change: 1 addition & 0 deletions website/docs/.vitepress/locales/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const koConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
},
{ text: "지연 평가", link: "/ko/guide/lazy-evaluation" },
{ text: "동시성 처리", link: "/ko/guide/handle-concurrency" },
{ text: "p-limit/p-map에서 마이그레이션", link: "/ko/guide/migrate-from-p-map" },
{ text: "에러 처리", link: "/ko/guide/error-handling" },
{ text: "메서드 체이닝", link: "/ko/guide/method-chaining" },
],
Expand Down
1 change: 1 addition & 0 deletions website/docs/.vitepress/locales/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const zhConfig: LocaleSpecificConfig<DefaultTheme.Config> = {
},
{ text: "惰性求值", link: "/zh/guide/lazy-evaluation" },
{ text: "并发处理", link: "/zh/guide/handle-concurrency" },
{ text: "从 p-limit/p-map 迁移", link: "/zh/guide/migrate-from-p-map" },
{ text: "错误处理", link: "/zh/guide/error-handling" },
{ text: "方法链", link: "/zh/guide/method-chaining" },
],
Expand Down
108 changes: 108 additions & 0 deletions website/docs/guide/migrate-from-p-map.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Migrate from p-limit / p-map

If you use `p-map` or `p-limit` to run async work with a concurrency limit over a
collection, FxTS covers the same job with `concurrentPool` — plus streaming,
early termination, and AsyncIterable sources that array-based mappers cannot
express.

## p-map → concurrentPool

```typescript
// p-map
import pMap from "p-map";
const results = await pMap(urls, (url) => fetchItem(url), { concurrency: 5 });

// FxTS
import { concurrentPool, map, pipe, toArray, toAsync } from "@fxts/core";
const results = await pipe(
toAsync(urls),
map((url) => fetchItem(url)),
concurrentPool(5),
toArray,
);
```

`concurrentPool` has the same semantics as `p-map`: a pool of at most `n`
in-flight tasks where a new task starts as soon as one settles, results in input
order, and the first rejection aborts the run.

**Pick `concurrentPool`, not `concurrent`, when migrating.** `concurrent`
evaluates in fixed windows of `n` (the next window starts after the previous one
finishes), which is useful for batch semantics but has different throughput
characteristics than a `p-map`-style pool on uneven workloads.

## p-limit over a collection → concurrentPool

The common `p-limit` pattern — limiting a mapped collection — is the same
migration:

```typescript
// p-limit
import pLimit from "p-limit";
const limit = pLimit(5);
const results = await Promise.all(ids.map((id) => limit(() => loadUser(id))));

// FxTS
const results = await pipe(
toAsync(ids),
map((id) => loadUser(id)),
concurrentPool(5),
toArray,
);
```

## What does not map

Being honest about the boundaries:

- **A shared ad-hoc limiter** — one `limit` instance throttling unrelated call
sites across a codebase — is not what a pipeline library models. Keep
`p-limit` for that.
- **`stopOnError: false`** (collecting an `AggregateError`) has no direct
equivalent; FxTS propagates the first error. To collect outcomes instead,
return result objects from the mapper:
`map(async (url) => fetchItem(url).then((v) => ({ ok: true as const, v }), (e) => ({ ok: false as const, e })))`.
- **`AbortSignal`** is not taken as an option. Laziness covers the usual cases:
stopping consumption stops the pipeline.

## What you gain over p-map

Results can be **consumed as a stream** instead of waiting for the whole array:

```typescript
import { concurrentPool, each, map, pipe, toAsync } from "@fxts/core";

await pipe(
toAsync(urls),
map((url) => fetchItem(url)),
concurrentPool(5),
each((item) => render(item)), // handled as each result arrives, in order
);
```

Pipelines **terminate early** — combine with `take` to stop scheduling work once
you have enough:

```typescript
import {
concurrentPool,
filter,
map,
pipe,
take,
toArray,
toAsync,
} from "@fxts/core";

const firstThree = await pipe(
toAsync(candidates),
map((c) => probe(c)),
concurrentPool(5),
filter((r) => r.alive),
take(3), // stops pulling once three passed - remaining work is not started
toArray,
);
```

And the source does not have to be an array — any `AsyncIterable` works
(paginated APIs, streams), so the whole input never needs to be materialized.
108 changes: 108 additions & 0 deletions website/docs/ja/guide/migrate-from-p-map.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# p-limit / p-map からの移行

コレクションに同時実行数の制限をかけて非同期処理を実行するために `p-map` や
`p-limit` を使っているなら、FxTS の `concurrentPool` が同じ役割を果たします —
さらに、配列ベースのマッパーでは表現できないストリーミング、早期終了、
AsyncIterable ソースも得られます。

## p-map → concurrentPool

```typescript
// p-map
import pMap from "p-map";
const results = await pMap(urls, (url) => fetchItem(url), { concurrency: 5 });

// FxTS
import { concurrentPool, map, pipe, toArray, toAsync } from "@fxts/core";
const results = await pipe(
toAsync(urls),
map((url) => fetchItem(url)),
concurrentPool(5),
toArray,
);
```

`concurrentPool` は `p-map` と同じセマンティクスです: 最大 `n` 個のタスクが実行中の
プールで、1 つが完了するとすぐ次のタスクが開始され、結果は入力順で返り、最初の失敗が
実行を中断します。

**移行の際は `concurrent` ではなく `concurrentPool` を選んでください。**
`concurrent` は `n` 個単位の固定ウィンドウで評価します(前のウィンドウが終わってから
次が始まる)。バッチのセマンティクスが必要な場合には有用ですが、処理時間が不均一な
ワークロードでは `p-map` 方式のプールとスループット特性が異なります。

## コレクションに対する p-limit → concurrentPool

最も一般的な `p-limit` パターン — マッピングされたコレクションへの制限 — も同じ
方法で移行できます:

```typescript
// p-limit
import pLimit from "p-limit";
const limit = pLimit(5);
const results = await Promise.all(ids.map((id) => limit(() => loadUser(id))));

// FxTS
const results = await pipe(
toAsync(ids),
map((id) => loadUser(id)),
concurrentPool(5),
toArray,
);
```

## 対応しないもの

境界を正直に述べると:

- **共有される ad-hoc リミッター** — 1 つの `limit` インスタンスでコードベース各所の
無関係な呼び出しを制限するパターン — はパイプラインライブラリがモデル化する対象では
ありません。その用途には `p-limit` を使い続けてください。
- **`stopOnError: false`**(`AggregateError` への収集)の直接の対応物はありません。
FxTS は最初のエラーを伝播します。結果を収集したい場合はマッパーで結果オブジェクトを
返してください:
`map(async (url) => fetchItem(url).then((v) => ({ ok: true as const, v }), (e) => ({ ok: false as const, e })))`.
- **`AbortSignal`** オプションはありません。一般的なケースは遅延評価がカバーします:
消費を止めればパイプラインも止まります。

## p-map に対して得られるもの

配列全体を待たずに、結果を**ストリームとして消費**できます:

```typescript
import { concurrentPool, each, map, pipe, toAsync } from "@fxts/core";

await pipe(
toAsync(urls),
map((url) => fetchItem(url)),
concurrentPool(5),
each((item) => render(item)), // 結果が届くたびに、順序どおりに処理
);
```

パイプラインは**早期終了**します — `take` と組み合わせれば、十分な数が揃った時点で
タスクのスケジューリング自体が止まります:

```typescript
import {
concurrentPool,
filter,
map,
pipe,
take,
toArray,
toAsync,
} from "@fxts/core";

const firstThree = await pipe(
toAsync(candidates),
map((c) => probe(c)),
concurrentPool(5),
filter((r) => r.alive),
take(3), // 3 つ得られたら取得を止め、残りの処理は開始されません
toArray,
);
```

そしてソースは配列である必要はありません — どんな `AsyncIterable` でも動作するため
(ページネーション API、ストリーム)、入力全体をメモリに実体化する必要がありません。
106 changes: 106 additions & 0 deletions website/docs/ko/guide/migrate-from-p-map.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# p-limit / p-map에서 마이그레이션

컬렉션에 동시성 제한을 걸어 비동기 작업을 실행하기 위해 `p-map`이나 `p-limit`을
사용하고 있다면, FxTS의 `concurrentPool`이 같은 일을 해냅니다 — 여기에 배열 기반
매퍼로는 표현할 수 없는 스트리밍, 조기 종료, AsyncIterable 소스까지 더해집니다.

## p-map → concurrentPool

```typescript
// p-map
import pMap from "p-map";
const results = await pMap(urls, (url) => fetchItem(url), { concurrency: 5 });

// FxTS
import { concurrentPool, map, pipe, toArray, toAsync } from "@fxts/core";
const results = await pipe(
toAsync(urls),
map((url) => fetchItem(url)),
concurrentPool(5),
toArray,
);
```

`concurrentPool`은 `p-map`과 동일한 의미론을 가집니다: 최대 `n`개의 작업이 실행
중인 풀에서 하나가 끝나는 즉시 다음 작업이 시작되고, 결과는 입력 순서대로
반환되며, 첫 번째 실패가 실행을 중단시킵니다.

**마이그레이션할 때는 `concurrent`가 아니라 `concurrentPool`을 선택하세요.**
`concurrent`는 `n`개 단위의 고정 윈도우로 평가하며(이전 윈도우가 끝나야 다음
윈도우가 시작), 배치 의미론이 필요할 때는 유용하지만 작업 시간이 불균일한
워크로드에서는 `p-map` 방식의 풀과 처리량 특성이 다릅니다.

## 컬렉션에 대한 p-limit → concurrentPool

가장 흔한 `p-limit` 패턴 — 매핑된 컬렉션에 제한을 거는 것 — 도 같은 방식으로
마이그레이션됩니다:

```typescript
// p-limit
import pLimit from "p-limit";
const limit = pLimit(5);
const results = await Promise.all(ids.map((id) => limit(() => loadUser(id))));

// FxTS
const results = await pipe(
toAsync(ids),
map((id) => loadUser(id)),
concurrentPool(5),
toArray,
);
```

## 대응되지 않는 것

경계를 정직하게 말하면:

- **공유되는 ad-hoc 리미터** — 하나의 `limit` 인스턴스로 코드베이스 곳곳의 서로
무관한 호출을 제한하는 패턴 — 는 파이프라인 라이브러리가 모델링하는 대상이
아닙니다. 그 용도로는 `p-limit`을 유지하세요.
- **`stopOnError: false`** (`AggregateError`로 수집)의 직접 대응물은 없습니다.
FxTS는 첫 에러를 전파합니다. 결과를 수집하려면 매퍼에서 결과 객체를 반환하세요:
`map(async (url) => fetchItem(url).then((v) => ({ ok: true as const, v }), (e) => ({ ok: false as const, e })))`.
- **`AbortSignal`** 옵션은 받지 않습니다. 일반적인 경우는 lazy 특성이 커버합니다:
소비를 멈추면 파이프라인도 멈춥니다.

## p-map 대비 얻는 것

전체 배열을 기다리지 않고 결과를 **스트림으로 소비**할 수 있습니다:

```typescript
import { concurrentPool, each, map, pipe, toAsync } from "@fxts/core";

await pipe(
toAsync(urls),
map((url) => fetchItem(url)),
concurrentPool(5),
each((item) => render(item)), // 결과가 도착하는 대로, 순서대로 처리
);
```

파이프라인은 **조기 종료**됩니다 — `take`와 결합하면 충분히 모인 시점에 작업
스케줄링 자체가 멈춥니다:

```typescript
import {
concurrentPool,
filter,
map,
pipe,
take,
toArray,
toAsync,
} from "@fxts/core";

const firstThree = await pipe(
toAsync(candidates),
map((c) => probe(c)),
concurrentPool(5),
filter((r) => r.alive),
take(3), // 3개를 얻으면 당기기를 멈추고, 남은 작업은 시작되지 않습니다
toArray,
);
```

그리고 소스가 배열일 필요가 없습니다 — 어떤 `AsyncIterable`이든 동작하므로
(페이지네이션 API, 스트림), 전체 입력을 메모리에 실체화할 필요가 없습니다.
Loading
Loading