clean-typescript-async
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseClean Async TypeScript
简洁的异步TypeScript
Async code should make ordering, ownership, and failure behavior explicit. Race-prone behavior deserves small units and direct tests.
异步代码应使执行顺序、资源归属和失败行为清晰明确。存在竞态风险的代码应拆分为小单元并进行直接测试。
A1: Isolate Async Workflows
A1:隔离异步工作流
Keep orchestration separate from pure transformation. Parse, validate, map, and calculate in synchronous helpers when possible; let async functions coordinate I/O.
ts
async function importUsers(file: FileHandle, repository: UserRepository) {
const rows = await file.readRows();
const users = rows.map(parseUserRow);
await repository.saveMany(users);
}将编排逻辑与纯转换逻辑分离。尽可能在同步辅助函数中进行解析、验证、映射和计算;让异步函数仅负责协调I/O操作。
ts
async function importUsers(file: FileHandle, repository: UserRepository) {
const rows = await file.readRows();
const users = rows.map(parseUserRow);
await repository.saveMany(users);
}A2: Make Ordering Explicit
A2:明确执行顺序
Use sequential when order matters and when operations are independent. Do not rely on array callbacks with hidden promise behavior.
awaitPromise.allts
// Bad - promises are created but not awaited by forEach
users.forEach(async (user) => {
await sendInvite(user);
});
// Good - independent work is explicit
await Promise.all(users.map((user) => sendInvite(user)));当顺序重要时使用顺序,当操作相互独立时使用。不要依赖带有隐藏Promise行为的数组回调。
awaitPromise.allts
// Bad - promises are created but not awaited by forEach
users.forEach(async (user) => {
await sendInvite(user);
});
// Good - independent work is explicit
await Promise.all(users.map((user) => sendInvite(user)));A3: Avoid Shared Mutable State Across Awaits
A3:避免在await边界间使用共享可变状态
Mutable variables that survive across boundaries invite races and stale assumptions. Prefer local values, immutable updates, or a single owner for shared state.
awaitts
// Bad - concurrent calls can corrupt shared state
let cachedUser: User | undefined;
async function getUser(id: string) {
if (!cachedUser) {
cachedUser = await fetchUser(id);
}
return cachedUser;
}
// Good - cache key and mutation ownership are explicit
const userCache = new Map<string, Promise<User>>();
function getUser(id: string) {
const existing = userCache.get(id);
if (existing) {
return existing;
}
const request = fetchUser(id).catch((error: unknown) => {
userCache.delete(id);
throw error;
});
userCache.set(id, request);
return request;
}If a cache stores promises, define the failure policy. Most request caches should evict rejected promises so one transient failure does not poison every future lookup.
跨越边界的可变变量容易引发竞态条件和过期假设。优先使用局部值、不可变更新,或由单一所有者管理共享状态。
awaitts
// Bad - concurrent calls can corrupt shared state
let cachedUser: User | undefined;
async function getUser(id: string) {
if (!cachedUser) {
cachedUser = await fetchUser(id);
}
return cachedUser;
}
// Good - cache key and mutation ownership are explicit
const userCache = new Map<string, Promise<User>>();
function getUser(id: string) {
const existing = userCache.get(id);
if (existing) {
return existing;
}
const request = fetchUser(id).catch((error: unknown) => {
userCache.delete(id);
throw error;
});
userCache.set(id, request);
return request;
}如果缓存存储Promise,需定义失败处理策略。大多数请求缓存应清除被拒绝的Promise,避免一次临时失败影响后续所有查询。
A4: Make Cancellation, Timeouts, Retries, And Fallbacks Visible
A4:显式处理取消、超时、重试和回退
Async failure policy is behavior. Keep retry counts, timeout durations, abort signals, and fallback choices named and near the call that owns the policy.
ts
const USER_LOOKUP_TIMEOUT_MS = 2_000;
await fetchUser(userId, {
signal: AbortSignal.timeout(USER_LOOKUP_TIMEOUT_MS),
});Do not hide broad fallbacks in shared clients unless every caller truly wants the same behavior.
异步失败策略是核心行为。将重试次数、超时时长、中止信号和回退选项命名,并放在负责该策略的调用附近。
ts
const USER_LOOKUP_TIMEOUT_MS = 2_000;
await fetchUser(userId, {
signal: AbortSignal.timeout(USER_LOOKUP_TIMEOUT_MS),
});不要在共享客户端中隐藏通用回退逻辑,除非所有调用方确实需要相同的行为。
A5: Test Race-Prone Behavior
A5:测试存在竞态风险的行为
When code depends on ordering, cancellation, retry, timeout, or deduplication, test that behavior directly with controlled promises, fake timers, or in-memory fakes.
ts
test("deduplicates concurrent user fetches", async () => {
const fetchUser = vi.fn().mockResolvedValue({ id: "user-1" });
const users = createUserLoader(fetchUser);
await Promise.all([users.get("user-1"), users.get("user-1")]);
expect(fetchUser).toHaveBeenCalledTimes(1);
});Flaky async tests are usually a design signal: make the event, clock, promise, or external dependency controllable.
当代码依赖于执行顺序、取消、重试、超时或去重逻辑时,使用受控Promise、模拟计时器或内存模拟直接测试这些行为。
ts
test("deduplicates concurrent user fetches", async () => {
const fetchUser = vi.fn().mockResolvedValue({ id: "user-1" });
const users = createUserLoader(fetchUser);
await Promise.all([users.get("user-1"), users.get("user-1")]);
expect(fetchUser).toHaveBeenCalledTimes(1);
});不稳定的异步测试通常是设计问题的信号:需使事件、时钟、Promise或外部依赖可控制。