mcpField
A
async-over-callbacks

async-over-callbacks

github.com/marius-townhouse开发642 次安装38 周前更新
代码仓库

关于 async-over-callbacks

这项 TypeScript 技能指导开发者优先使用 async/await 和 Promise 编写异步代码,并介绍组合操作、错误处理及封装回调 API。

主要功能

  1. 01新代码优先使用 async/await
  2. 02组合顺序与并发任务
  3. 03用 try/catch 处理错误
  4. 04将回调 API 封装为 Promise

适用场景

  1. 01重构嵌套回调链
  2. 02协调多个异步请求
  3. 03处理异步代码中的失败

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/marius-townhouse/effective-typescript-skills --skill async-over-callbacks

安装后在 Claude Code 中输入/async-over-callbacks确认已加载。

详细介绍

使用 async 函数,而不是回调

概述

优先使用 async/await,而不是回调,这样代码更简洁,类型流转也更顺畅。

回调会产生层层嵌套、难以理解的代码。Promise 和 async/await 能扁平化代码结构,让类型自然流转,并改进错误处理。

何时使用此技巧

  • 编写任何异步代码时
  • 想要使用回调风格的 API 时
  • 链接多个异步操作时
  • 需要组合并发操作时
  • 使用返回 Promise 的 API 时

铁律

ALWAYS prefer async/await over callbacks for new code.

请记住:

  • async/await 是 Promise 的语法糖
  • 类型会自动通过 Promise 流转
  • 使用 try/catch 能更简洁地处理错误
  • 并发操作易于组合

检测:回调金字塔

如果看到嵌套回调(“厄运金字塔”),请重构为 async/await:

// ❌ Callback hell - hard to read, types don't flow well
fetchURL(url1, function(response1) {
fetchURL(url2, function(response2) {
fetchURL(url3, function(response3) {
// ... deeply nested
console.log(1);
});
console.log(2);
});
console.log(3);
});
console.log(4);
// Logs: 4, 3, 2, 1 (confusing order!)

async/await 解决方案

// ✅ Clean, flat, readable
async function fetchPages() {
const response1 = await fetch(url1);
const response2 = await fetch(url2);
const response3 = await fetch(url3);
// Execution order matches code order
}

类型为何能更顺畅地流转

回调需要手动标注类型
// ❌ Callbacks - you must annotate types
function fetchUser(
  id: string,
  callback: (user: User | null, error?: Error) => void
) {
  // ...
}

fetchUser('123', (user, error) => {
  if (error) { /* handle */ }
  if (user) { /* use user */ }
});
Promise 会自动携带类型
// ✅ Promises - types flow through
async function fetchUser(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  return response.json();  // TypeScript knows this returns Promise<User>
}

const user = await fetchUser('123');
//    ^? const user: User

组合异步操作

顺序操作
async function getFullUserData(id: string) {
  const user = await fetchUser(id);
  const posts = await fetchPosts(user.id);
  const comments = await fetchComments(posts);
  return { user, posts, comments };
}
// Return type is automatically inferred
使用 Promise.all 执行并发操作
async function fetchAllPages() {
  // Run all fetches concurrently, wait for all to complete
  const [page1, page2, page3] = await Promise.all([
    fetch(url1),
    fetch(url2),
    fetch(url3),
  ]);
  // Types are inferred: [Response, Response, Response]
}
使用 Promise.race 处理竞争条件
async function fetchWithTimeout(url: string, ms: number) {
  return Promise.race([
    fetch(url),
    new Promise<never>((_, reject) =>
      setTimeout(() => reject(new Error('Timeout')), ms)
    ),
  ]);
}

错误处理

回调:错误优先约定(手动处理)
// ❌ Manual error handling, easy to forget
fetchData(url, (error, data) => {
  if (error) {
    console.error(error);
    return;
  }
  // use data
});
async/await:自然地使用 try/catch
// ✅ Standard exception handling
async function fetchData(url: string) {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    console.error('Fetch failed:', error);
    throw error;  // Re-throw or handle
  }
}

常见模式

始终从 async 函数返回 Promise
// ❌ Inconsistent return type
function getQuote(ticker: string) {
  if (cache[ticker]) {
    return cache[ticker];  // Returns number
  }
  return fetch(`/quote?t=${ticker}`)
    .then(r => r.json());  // Returns Promise<number>
}
// Type: number | Promise<number> - confusing!

// ✅ Consistent Promise return
async function getQuote(ticker: string): Promise<number> {
  if (cache[ticker]) {
    return cache[ticker];  // Automatically wrapped in Promise
  }
  const response = await fetch(`/quote?t=${ticker}`);
  return response.json();
}
标注返回类型以提升清晰度
// ✅ Explicit return type catches mistakes
async function fetchUser(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  // TypeScript ensures we return a User
  return response.json();
}
使用 Promise.allSettled 处理部分失败
async function fetchAllUsers(ids: string[]) {
  const results = await Promise.allSettled(
    ids.map(id => fetchUser(id))
  );
  
  // Handle both successes and failures
  const users: User[] = [];
  for (const result of results) {
    if (result.status === 'fulfilled') {
      users.push(result.value);
    } else {
      console.error('Failed:', result.reason);
    }
  }
  return users;
}

将回调转换为 Promise

使用 util.promisify(Node.js)
import { promisify } from 'util';
import { readFile } from 'fs';

const readFileAsync = promisify(readFile);

async function loadConfig() {
  const data = await readFileAsync('config.json', 'utf-8');
  return JSON.parse(data);
}
手动将回调转换为 Promise
function fetchURLAsync(url: string): Promise<string> {
  return new Promise((resolve, reject) => {
    fetchURL(url, (response, error) => {
      if (error) {
        reject(error);
      } else {
        resolve(response);
      }
    });
  });
}

async 函数中的立即执行

// async functions return a Promise, even with immediate values
async function getValue(): Promise<number> {
  return 42;  // Wrapped in Promise.resolve(42)
}

// To unwrap, you must await
const value = await getValue();
//    ^? const value: number

抵制压力的准则

1. “回调速度更快”

压力:“Promise 有额外开销,回调的性能更好。”

**回应:**额外开销微不足道。代码清晰度和类型安全更重要。

**行动:**使用 async/await。如果怀疑存在性能问题,再进行性能分析。

2. “API 只支持回调”

压力:“这个库使用回调,我们也只能用回调。”

**回应:**将回调 API 封装为 Promise。

**行动:**使用 promisify 或创建 Promise 封装。

3. “我们到处都在用回调”

压力:“这样能与现有代码保持一致。”

**回应:**逐步迁移。新代码应使用 async/await。

**行动:**封装旧 API,用 async/await 编写新代码。

危险信号——停下来重新考虑

  • 嵌套回调(厄运金字塔)
  • 有时返回值、有时返回 Promise 的函数
  • 使用错误优先回调手动处理错误
  • 不必要地混用 async/await 和 .then() 链

常见的合理化说辞(都不成立)

借口事实
“回调更简单”async/await 可读性更好,也更易维护
“Promise 的额外开销太大”实际上微不足道,清晰度更重要
“我们的团队熟悉回调”async/await 是现代 JavaScript 的标准写法

快速参考

模式回调async/await
顺序操作嵌套回调顺序 await
并发操作手动跟踪Promise.all
错误处理错误优先约定try/catch
类型推断手动标注自动流转
组合困难自然

归根结底

async/await 能生成更简洁的代码,并提供更好的类型推断。

回调会产生层层嵌套的代码金字塔,类型也难以顺畅流转。Promise 和 async/await 能扁平化代码结构、自然地组合操作、使用标准的 try/catch 处理错误,并让 TypeScript 自动推断类型。所有新的异步代码都应使用 async/await。

参考资料

根据 Dan Vanderkam 的《Effective TypeScript》第 27 条:使用 async 函数而不是回调,以改善类型流转。

评论

支持 Markdown