npm package: @snowykami/use-async-task
Description: A lightweight React Hook for managing complex async task states with global sharing, caching, retry, and race condition control.
Author: snowykami
Latest version: 0.0.3

# @snowykami/use-async-task

![npm](https://img.shields.io/npm/v/@snowykami/use-async-task)
![license](https://img.shields.io/badge/license-MIT-green)
![npm peer dependency version](https://img.shields.io/npm/dependency-version/@snowykami/use-async-task/peer/react)

一个轻量的 React Hook，用于管理复杂异步任务的状态。支持全局共享、缓存、轮询、重试与竞态控制。

## 核心特性

- 🌍 **跨组件共享状态** - 多个组件使用相同 `taskKey` 会自动共享同一份任务状态
- 💾 **智能缓存** - 可配置的缓存时间，避免重复请求相同数据
- 🔄 **自动重试** - 内置失败重试机制，支持可配置的重试次数
- ⚡ **竞态控制** - 快速请求时仅应用最后一次的请求结果，旧请求自动丢弃
- 🔁 **轮询支持** - 内置轮询间隔，支持定时自动刷新数据
- 🎯 **完整类型支持** - 100% TypeScript，提供完整的类型定义
- 📦 **超轻量** - 0 外部依赖，仅依赖 React

## 安装

```bash
npm install @snowykami/use-async-task
# 或者使用 yarn
yarn add @snowykami/use-async-task
# 或者使用 pnpm
pnpm add @snowykami/use-async-task
```

## 快速开始

```typescript
import { useAsyncTask } from '@snowykami/use-async-task';

function MyComponent() {
  const { data, loading, error, execute } = useAsyncTask(
    async (userId: string) => {
      const res = await fetch(`/api/users/${userId}`);
      return res.json();
    },
    {
      immediate: false,
      maxRetries: 2,
      cacheTime: 10000, // 10秒缓存
      taskKey: (userId) => `user-${userId}`,
    }
  );

  return (
    <div>
      {loading && <p>加载中...</p>}
      {error && <p>错误: {error}</p>}
      {data && <p>用户: {data.name}</p>}
      <button onClick={() => execute('123')}>获取用户 123</button>
    </div>
  );
}
```

## API 文档

### `useAsyncTask(action, options?, initialTaskKey?)`

#### 参数

- **`action`** `(...args: Args) => Promise<T>`
  - 要执行的异步函数
  - 接收 `execute()` 传入的参数

- **`options`** (可选)
  ```typescript
  {
    // 挂载时或依赖变化时是否自动执行
    immediate?: boolean;

    // 依赖列表（类似 useEffect）
    dependencies?: DependencyList;

    // 依赖变化时获取最新参数
    getArgs?: () => Args;

    // 轮询间隔（毫秒），0 表示不轮询
    pollingInterval?: number;

    // 最大重试次数
    maxRetries?: number;

    // 缓存时间（毫秒），0 表示不缓存
    cacheTime?: number;

    // 任务标识
    // - 字符串：固定 key
    // - 函数：根据参数动态生成 key
    taskKey?: string | ((...args: Args) => string);
  }
  ```

- **`initialTaskKey`** (可选)
  - 初始任务 key（被 `options.taskKey` 覆盖）

#### 返回值

```typescript
{
  // 状态属性
  data: T | null;              // 解析后的数据
  loading: boolean;            // 加载状态
  error: TError | null;        // 错误对象（如果有）
  retryCount: number;          // 重试次数
  lastUpdated: number | null;  // 最后更新时间戳

  // 方法
  execute: (...args: Args) => Promise<T | void>;  // 手动执行任务
  cancel: () => void;          // 取消当前请求
  reset: () => void;           // 重置为初始状态
}
```

## 使用示例

### 1. 跨组件共享状态

使用相同 `taskKey` 的多个组件自动共享状态：

```typescript
// 组件 A
const { data: user } = useAsyncTask(
  (id: string) => fetchUser(id),
  {
    maxRetries: 3,
    taskKey: (id) => `user-${id}`,
  }
);

// 组件 B（并行运行）
const { data: user } = useAsyncTask(
  (id: string) => fetchUser(id),
  {
    maxRetries: 3,
    taskKey: (id) => `user-${id}`,  // 相同的 key！
  }
);

// 两个组件自动同步，当数据更新时一起重新渲染
```

### 2. 依赖变化时自动刷新

```typescript
const [page, setPage] = useState(1);

const { data: users } = useAsyncTask(
  (p: number) => fetchUsers(p),
  {
    immediate: true,
    dependencies: [page],
    getArgs: () => [page] as const,
    taskKey: (p) => `users-page-${p}`,
  }
);

// 改变 page 时自动触发新请求
```

### 3. 搜索 + 缓存 + 竞态控制

```typescript
const [query, setQuery] = useState('');

const { data: results } = useAsyncTask(
  async (q: string) => searchUsers(q),
  {
    immediate: true,
    dependencies: [query],
    getArgs: () => [query] as const,
    maxRetries: 1,
    cacheTime: 30000,  // 缓存 30 秒
    taskKey: (q) => `search-${q}`,
  }
);

// 快速输入：只处理最后一次搜索
// 重复搜索：30秒内使用缓存
```

### 4. 轮询 / 定时刷新

```typescript
const { data: stats } = useAsyncTask(
  () => fetchStats(),
  {
    immediate: true,
    pollingInterval: 5000,  // 每 5 秒刷新一次
    taskKey: 'dashboard-stats',
  }
);
```

### 5. 手动表单提交

```typescript
const { loading, error, execute } = useAsyncTask(
  async (formData: FormData) => submitForm(formData),
  { immediate: false }  // 不自动执行
);

const handleSubmit = async (data: FormData) => {
  const result = await execute(data);
  if (result) console.log('提交成功！');
};
```

## 全局状态共享原理

Hook 维护一个全局任务注册表，每个 `taskKey` 映射到一个共享状态：

```
taskKey: 'user-123'
  └─ TaskRecord
     ├─ state: { data, loading, error, ... }
     ├─ listeners: Set<Listener>  (所有订阅的组件)
     └─ [竞态控制信息]
```

当任何组件更新此状态时，所有订阅的组件自动重新渲染。

## 竞态控制原理

当多个请求同时进行时：

1. 每个请求获得一个序列号
2. 只有最新请求的结果会被应用
3. 旧请求的结果自动被丢弃

这确保 UI 总是显示最新请求的结果。

## 缓存策略

在每次 `execute()` 调用时检查缓存：

```
if (Date.now() - lastUpdated <= cacheTime) {
  // 使用缓存数据，跳过请求
  return cachedData;
} else {
  // 缓存过期，发起新请求
  // 用新数据更新缓存
}
```

不同的 `taskKey` 拥有独立的缓存。

## 最佳实践

1. **使用具有描述性的 taskKey** - 便于调试
   ```typescript
   taskKey: (userId) => `user-profile-${userId}`  // ✓ 好
   taskKey: (x) => `${x}`  // ✗ 模糊
   ```

2. **使用 dependencies 时总是提供 getArgs**
   ```typescript
   {
     dependencies: [page],
     getArgs: () => [page] as const,  // ✓ 提供新值
   }
   ```

3. **根据数据新鲜度设置合适的缓存时间**
   ```typescript
   cacheTime: 0,         // 不缓存（实时数据）
   cacheTime: 5000,      // 5 秒（不那么关键的数据）
   cacheTime: 300000,    // 5 分钟（参考数据）
   ```

4. **对不稳定的端点使用重试**
   ```typescript
   maxRetries: 0,   // 不重试（关键且需要快速失败）
   maxRetries: 3,   // 重试最多 3 次（标准）
   ```

## 浏览器支持

- ES2020+
- React 16.8+（需要 Hooks 支持）

## 许可证

MIT © snowykami

## 贡献

欢迎提交 Issue 和 PR！

## 更新日志

### v1.0.0 (首次发布)
- 核心 Hook 实现
- 全局状态共享
- 缓存系统
- 重试逻辑
- 轮询支持
- 竞态控制