[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-75028":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":11,"openIssues":13,"contributorsCount":14,"subscribersCount":14,"size":14,"stars1d":14,"stars7d":15,"stars30d":16,"stars90d":14,"forks30d":14,"starsTrendScore":14,"compositeScore":17,"rankGlobal":9,"rankLanguage":9,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":19,"hasPages":19,"topics":21,"createdAt":9,"pushedAt":9,"updatedAt":22,"readmeContent":23,"aiSummary":24,"trendingCount":14,"starSnapshotCount":14,"syncStatus":15,"lastSyncTime":25,"discoverSource":26},75028,"better-all","shuding\u002Fbetter-all","shuding","Better Promise.all with automatic dependency optimization",null,"TypeScript",1086,20,1,0,2,3,16.97,"MIT License",false,"main",[],"2026-06-12 02:03:31","# better-all\n\nPromise.all with automatic dependency optimization and full type inference.\n\n## Why?\n\nWhen you have tasks with dependencies, the common `Promise.all` pattern is sometimes inefficient:\n\n```typescript\n\u002F\u002F Common pattern: Sequential execution wastes time\nconst [a, b] = await Promise.all([getA(), getB()])  \u002F\u002F a: 1s, b: 10s → takes 10s\nconst c = await getC(a)                             \u002F\u002F c: 10s → takes 10s\n\u002F\u002F Total: 20 seconds\n```\n\nYou could optimize this manually by parallelizing `b` and `c`:\n\n```typescript\nconst a = await getA()               \u002F\u002F a: 1s -> takes 1s\nconst [b, c] = await Promise.all([   \u002F\u002F b: 10s, c: 10s -> takes 10s\n  getB(),\n  getC(a)\n])\n\u002F\u002F Total: 11 seconds\n```\n\nBut what if the durations of these methods change (i.e. unstable network latency)? Say `getA()` now takes 10 seconds and `getC()` takes 1 second. The previous manual optimization becomes suboptimal again, compared to the naive approach:\n\n```typescript\nconst a = await getA()              \u002F\u002F a: 10s -> takes 10s\nconst [b, c] = await Promise.all([  \u002F\u002F b: 10s, c: 1s -> takes 10s\n  getB(),\n  getC(a)\n])\n\u002F\u002F Total: 20 seconds\n\n\u002F\u002F Naive approach:\nconst [a, b] = await Promise.all([getA(), getB()])  \u002F\u002F a: 10s, b: 10s → takes 10s\nconst c = await getC(a)                             \u002F\u002F c: 1s → takes 1s\n\u002F\u002F Total: 11 seconds\n```\n\nTo correctly optimize such cases using `Promise.all`, you'd have to _manually analyze and declare the dependency graph_:\n\n```typescript\nconst [[a, c], b] = await Promise.all([\n  getA().then(a => getC(a).then(c => [a, c])),\n  getB()\n])\n```\n\nThis quickly becomes unmanageable in real-world scenarios with many tasks and complex dependencies, not to mention the loss of readability.\n\nIn real-world application code, there are more downsides of the naive approach and ad-hoc promise adjustments.\n[Give this a read](https:\u002F\u002Fgithub.com\u002Fshuding\u002Fbetter-all\u002Fdiscussions\u002F3) if you are still not convinced.\n\n## Better `Promise.all`\n\n**This library solves it automatically:**\n\n```typescript\nimport { all } from 'better-all'\n\nconst { a, b, c } = await all({\n  async a() { return getA() },               \u002F\u002F 1s\n  async b() { return getB() },               \u002F\u002F 10s\n  async c() { return getC(await this.$.a) }  \u002F\u002F 10s (waits for a)\n})\n\u002F\u002F Total: 11 seconds - optimal parallelization!\n```\n\n`all` automatically kicks off all tasks immediately, and when hitting an `await this.$.dependency`, it waits for that specific task to complete.\n\nThe magical `this.$` object gives you access to all other task results as promises, allowing you to express dependencies naturally.\n\nThe library ensures maximal parallelization automatically.\n\n## Installation\n\n```bash\nnpm install better-all\n# or\npnpm add better-all\n# or\nbun add better-all\n# or\nyarn add better-all\n```\n\n## Features\n\n- **Full type inference**: Both results and dependencies are fully typed\n- **Automatic maximal parallelization**: Independent tasks run in parallel\n- **Object-based API**: Minimal cognitive load, easy to read\n- **No hanging promises**: Avoids the uncaught dangling promises problem often seen in manual optimization\n- **Auto-abort on failure**: Cancel remaining tasks when one fails via `this.$signal`\n- **Debug mode with waterfall visualization**: See exactly how tasks execute with ASCII waterfall charts\n- **Early exit support**: Exit flows early when a result is determined\n- **Lightweight**: Minimal dependencies and small bundle size\n\n## API\n\n### `all(tasks, options?)`\n\nExecute tasks with automatic dependency resolution.\n\n- `tasks`: Object of async task functions\n- `options`: Optional configuration object\n  - `debug`: Set to `true` to output a waterfall chart showing task execution timeline\n  - `signal`: An `AbortSignal` to abort all tasks externally\n- Each task function receives:\n  - `this.$` - an object with promises for all task results\n  - `this.$signal` - an `AbortSignal` that aborts when any sibling task fails\n- Returns a promise that resolves to an object with all task results\n- Rejects if any task fails (like `Promise.all`)\n\n### `allSettled(tasks, options?)`\n\nExecute tasks with automatic dependency resolution, returning settled results for all tasks.\n\n- `tasks`: Object of async task functions\n- `options`: Optional configuration object\n  - `debug`: Set to `true` to output a waterfall chart showing task execution timeline\n  - `signal`: An `AbortSignal` to abort all tasks externally\n- Each task function receives:\n  - `this.$` - an object with promises for all task results\n  - `this.$signal` - an `AbortSignal` (only aborts on external signal, not on sibling failure)\n- Returns a promise that resolves to an object with all task results as `{ status: 'fulfilled', value }` or `{ status: 'rejected', reason }`\n- Never rejects - failed tasks are included in the result (like `Promise.allSettled`)\n- If a task depends on a failed task, the dependent task will also fail unless it catches the error\n\n### `flow\u003CR>(tasks, options?)`\n\nExecute tasks with automatic dependency resolution and early exit support.\n\n- **Type parameter `\u003CR>`**: **Required**. Specifies the return type that `$end()` must accept\n- `tasks`: Object of async task functions\n- `options`: Same as `all()` - optional configuration object\n- Each task function receives:\n  - `this.$` - an object with promises for all task results\n  - `this.$signal` - an `AbortSignal` for resource cleanup\n  - `this.$end(value: R)` - function to exit the entire flow early with a return value of type `R`\n- Returns a promise that resolves to `R | undefined`\n  - Returns the value passed to the first `$end()` call\n  - Returns `undefined` if no task calls `$end()`\n- See [Early Exit Flow](#early-exit-flow) for detailed usage\n\n## Examples\n\n### Basic Parallel Execution\n\n```typescript\nconst { a, b, c } = await all({\n  async a() { await sleep(1000); return 1 },\n  async b() { await sleep(1000); return 2 },\n  async c() { await sleep(1000); return 3 }\n})\n\n\u002F\u002F All three run in parallel\n\u002F\u002F Returns { a: 1, b: 2, c: 3 }\n```\n\n### With Dependencies\n\n```typescript\nconst { user, profile, settings } = await all({\n  async user() { return fetchUser(1) },\n  async profile() { return fetchProfile((await this.$.user).id) },\n  async settings() { return fetchSettings((await this.$.user).id) }\n})\n\n\u002F\u002F User runs first, then profile and settings run in parallel\n```\n\n## Type Safety\n\nFull TypeScript support with automatic type inference:\n\n```typescript\nconst result = await all({\n  async num() { return 42 },\n  async str() { return 'hello' },\n  async combined() {\n    const n = await this.$.num  \u002F\u002F n: number (auto-inferred!)\n    const s = await this.$.str  \u002F\u002F s: string (auto-inferred!)\n    return `${s}: ${n}`\n  }\n})\n\nresult.num       \u002F\u002F number\nresult.str       \u002F\u002F string\nresult.combined  \u002F\u002F string\n```\n\n### Complex Dependency Graph\n\n```typescript\nconst { a, b, c, d, e } = await all({\n  async a() { return 1 },\n  async b() { return 2 },\n  async c() { return (await this.$.a) + 10 },\n  async d() { return (await this.$.b) + 20 },\n  async e() { return (await this.$.c) + (await this.$.d) }\n})\n\n\u002F\u002F a and b run in parallel\n\u002F\u002F c waits for a, d waits for b (c and d can overlap)\n\u002F\u002F e waits for both c and d\n\n\u002F\u002F { a: 1, b: 2, c: 11, d: 22, e: 33 }\nconsole.log({ a, b, c, d, e })\n```\n\n### Stepped Dependency Chain\n\nIn this example, the `postsWithAuthor` task calls `await this.$.user` and `await this.$.posts` sequentially but there won't be any actual delays. The `all` function will always kick off all tasks as early as possible, so `posts` was already running while we awaited `this.$.user`:\n\n```typescript\nconst result = await all({\n  async user() {\n    return fetchUser(1)\n  },\n  async posts() {\n    return fetchPosts((await this.$.user).id)\n  },\n  async postsWithAuthor() {\n    const user = await this.$.user\n    console.log(`Fetched user: ${user.name}`)\n    const posts = await this.$.posts\n    return posts.map(post => ({ ...post, author: user.name }))\n  },\n})\n```\n\nThis still gives optimal parallelization.\n\n## Debug Mode\n\nEnable debug mode to visualize task execution with a waterfall chart:\n\n```typescript\nconst result = await all({\n  async config() {\n    await sleep(50)\n    return { apiUrl: 'https:\u002F\u002Fapi.example.com' }\n  },\n  async user() {\n    await sleep(120)\n    return { id: 1, name: 'Alice' }\n  },\n  async posts() {\n    const user = await this.$.user\n    await sleep(200)\n    return fetchPosts(user.id)\n  },\n  async profile() {\n    const user = await this.$.user\n    const config = await this.$.config\n    await sleep(80)\n    return fetchProfile(user.id, config.apiUrl)\n  },\n  async analytics() {\n    const posts = await this.$.posts\n    const profile = await this.$.profile\n    await sleep(40)\n    return computeAnalytics(posts, profile)\n  }\n}, { debug: true })\n```\n\nThis outputs an ASCII waterfall chart showing:\n- Task execution timeline\n- Task duration in milliseconds\n- Dependencies for each task\n- Visual representation of parallel vs sequential execution\n\nExample output:\n\n```\n╔════════════════════════════════════════════════════════════════════════════════╗\n║                           Task Execution Waterfall                             ║\n╠════════════════════════════════════════════════════════════════════════════════╣\n║ Total Duration: 364.54ms                                                       ║\n╚════════════════════════════════════════════════════════════════════════════════╝\n\nTask      │ Deps           │ Duration │ Timeline\n──────────┼────────────────┼──────────┼──────────────────────────────────────────────────────────────────\nconfig    │ -              │   51.4ms │ ████████                                                         \nuser      │ -              │  121.4ms │ ████████████████████                                             \nposts     │ user           │  322.6ms │ ░░░░░░░░░░░░░░░░░░░░██████████████████████████████████████       \nprofile   │ user, config   │  202.9ms │ ░░░░░░░░░░░░░░░░░░░░███████████████████                          \nanalytics │ posts, profile │  364.4ms │ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░███████\n\nLegend: █ = active (fulfilled), ▓ = active (rejected), ░ = waiting on dependency\n```\n\nThe enhanced waterfall visualization shows:\n- **█** (solid bars) = Active execution time when the task is running its code\n- **░** (light shade) = Waiting time when the task is blocked on a dependency\n- **▓** (dashed bars) = Active execution for tasks that failed\n\nThis makes it easy to:\n- Distinguish between active execution vs waiting on dependencies\n- Identify which tasks are running in parallel\n- See exactly how long each task actively executes vs waits\n- Understand the dependency chain and blocking relationships\n- Spot opportunities for optimization (e.g., tasks with long wait times)\n\n## Error Handling\n\n### With `all()`\n\nErrors propagate to dependent tasks automatically, similar to `Promise.all`:\n\n```typescript\ntry {\n  await all({\n    async a() { throw new Error('Failed') },\n    async b() { return (await this.$.a) + 1 }\n  })\n} catch (err) {\n  console.error(err) \u002F\u002F Error: Failed\n}\n```\n\n### With `allSettled()`\n\nAll tasks complete and return their settled state, never rejecting:\n\n```typescript\nconst result = await allSettled({\n  async a() { return 1 },\n  async b() { throw new Error('Task b failed') },\n  async c() { return 3 }\n})\n\n\u002F\u002F result.a: { status: 'fulfilled', value: 1 }\n\u002F\u002F result.b: { status: 'rejected', reason: Error('Task b failed') }\n\u002F\u002F result.c: { status: 'fulfilled', value: 3 }\n\nif (result.a.status === 'fulfilled') {\n  console.log(result.a.value) \u002F\u002F 1\n}\n\nif (result.b.status === 'rejected') {\n  console.error(result.b.reason) \u002F\u002F Error: Task b failed\n}\n```\n\n### Handling Dependency Failures with `allSettled()`\n\nWhen a task depends on a failed task, it will also fail unless the error is caught:\n\n```typescript\nconst result = await allSettled({\n  async a() { throw new Error('a failed') },\n  async b() {\n    \u002F\u002F This will fail because 'a' failed\n    const aValue = await this.$.a\n    return aValue + 10\n  },\n  async c() {\n    \u002F\u002F This handles the error and succeeds\n    try {\n      const aValue = await this.$.a\n      return aValue + 10\n    } catch (err) {\n      return 'fallback value'\n    }\n  }\n})\n\n\u002F\u002F result.a: { status: 'rejected', reason: Error('a failed') }\n\u002F\u002F result.b: { status: 'rejected', reason: Error('a failed') }\n\u002F\u002F result.c: { status: 'fulfilled', value: 'fallback value' }\n```\n\n## Abort Signal\n\nWhen a task fails in `all()`, you may want to cancel other running tasks to avoid wasting resources (e.g., API calls, LLM requests).\n\nEach task receives `this.$signal` - an `AbortSignal` that gets aborted when any sibling task fails:\n\n```typescript\nconst result = await all({\n  async fetchUser() {\n    const res = await fetch('\u002Fapi\u002Fuser', { signal: this.$signal })\n    return res.json()\n  },\n  async fetchPosts() {\n    \u002F\u002F If fetchUser fails, this.$signal will be aborted\n    const res = await fetch('\u002Fapi\u002Fposts', { signal: this.$signal })\n    return res.json()\n  }\n})\n```\n\nYou can also pass an external signal to respect parent abort controllers:\n\n```typescript\nconst controller = new AbortController()\n\nconst result = await all({\n  async a() { return fetchData(this.$signal) },\n  async b() { return fetchMoreData(this.$signal) }\n}, { signal: controller.signal })\n```\n\n**Note:** `allSettled()` does NOT auto-abort on task failure (to preserve its \"wait for all\" behavior), but external signal abort still works.\n\n## Early Exit Flow\n\n`flow` allows you to exit early from complex async flows when a task determines the final result. This is useful for optimization patterns like:\n\n- **Cache checks**: Exit early if cached data is available\n- **Racing operations**: Return the first successful result\n- **Conditional computations**: Skip remaining work based on intermediate results\n\n### `flow(tasks, options?)`\n\nExecute tasks with automatic dependency resolution, but allow any task to end the entire flow early by calling `this.$end(value)`.\n\n**Key behaviors:**\n- All tasks start together in parallel (same as `all()`)\n- First task to call `this.$end(value)` determines the return value\n- After `$end()` is called, other tasks that try to access dependencies will receive errors (caught silently)\n- Real errors (not from `$end`) still propagate to the caller\n- Integrates with `this.$signal` for resource cleanup\n\n### Early Exit from Cache\n\n```typescript\nimport { flow } from 'better-all'\n\nconst data = await flow\u003CYourDataType>({\n  async checkCache() {\n    const cached = await getFromCache('key')\n    if (cached) this.$end(cached)  \u002F\u002F Exit early with cached data\n    return null\n  },\n  async fetchFromApi() {\n    const user = await this.$.checkCache  \u002F\u002F Will throw if cache hit\n    return await fetchExpensiveData()\n  },\n  async processData() {\n    const apiData = await this.$.fetchFromApi\n    this.$end(transform(apiData))\n  }\n})\n```\n\n### Racing Operations\n\n```typescript\nconst result = await flow\u003CResponseData>({\n  async fetchFromPrimary() {\n    await sleep(100)\n    const data = await fetch('\u002Fapi\u002Fprimary')\n    this.$end(await data.json())\n  },\n  async fetchFromBackup() {\n    await sleep(500)\n    const data = await fetch('\u002Fapi\u002Fbackup')\n    this.$end(await data.json())\n  }\n})\n\u002F\u002F Returns data from whichever endpoint responds first\n```\n\n### Conditional Early Exit\n\n```typescript\nconst result = await flow\u003C{ error: string } | ProcessedData>({\n  async validateInput() {\n    const isValid = await validate(input)\n    if (!isValid) this.$end({ error: 'Invalid input' })\n    return input\n  },\n  async processData() {\n    const validInput = await this.$.validateInput\n    const processed = await heavyComputation(validInput)\n    this.$end({ success: true, data: processed })\n  }\n})\n```\n\n### Return Type\n\nYou must specify the return type as a type parameter to `flow`:\n\n```typescript\nconst result = await flow\u003Cnumber | string>({\n  async task1() {\n    this.$end(42)  \u002F\u002F number\n    return 1\n  },\n  async task2() {\n    this.$end('hello')  \u002F\u002F string\n    return 'world'\n  }\n})\n\u002F\u002F result: number | string | undefined\n```\n\nTo explicitly allow `undefined` as a return value:\n\n```typescript\n\u002F\u002F Explicitly allow undefined\nconst result = await flow\u003Cstring | undefined>({\n  async task1() {\n    const data = await getData()\n    if (!data) this.$end(undefined)  \u002F\u002F ✅ OK: undefined is in the type parameter\n    this.$end(data)\n  }\n})\n\u002F\u002F result: string | undefined\n```\n\n**⚠️ Important Notes:**\n- The type parameter `\u003CR>` is **required** and specifies what type `$end()` accepts\n- If you want to call `$end(undefined)`, you must explicitly include `undefined` in `R` (e.g., `flow\u003Cundefined>` or `flow\u003Cstring | undefined>`)\n- If no task calls `this.$end()`, the flow will return `undefined`\n- Once `$end()` is called, subsequent dependency accesses will fail (but are caught silently)\n- `$end()` stops the current task execution (throws internally)\n\n## Development\n\n```bash\npnpm install     # Install dependencies\npnpm test        # Run tests\npnpm build       # Build\n```\n\n## Author\n\n[Shu Ding](https:\u002F\u002Fshud.in)\n\n## License\n\nMIT\n","better-all 是一个用于优化 Promise 依赖关系的库，能够自动进行任务调度以实现最高效的并行处理。其核心功能包括全类型推断、自动最大化并行化以及基于对象的简洁API设计，通过特殊的 `this.$` 对象来自然地表达任务间的依赖关系，从而避免了手动管理复杂的依赖图谱。适用于需要处理多个异步操作且这些操作间存在依赖关系的应用场景，特别是在网络延迟不稳定导致任务执行时间变化的情况下，能显著提高代码效率和可读性。","2026-06-11 03:51:59","high_star"]