一键导入
async-over-callbacks
Use when writing asynchronous code. Use when tempted to use callbacks. Use when composing multiple async operations.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Use when writing asynchronous code. Use when tempted to use callbacks. Use when composing multiple async operations.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use when defining global types. Use when augmenting window. Use when typing environment variables. Use when working with build-time constants. Use when configuring type definitions.
Use when migrating JavaScript to TypeScript. Use when gradually adopting TypeScript. Use when working with mixed codebases. Use when converting large projects. Use when teams are learning TypeScript.
Use when creating types from example data. Use when types don't match all cases. Use when API responses vary.
Use when writing type annotations on variables. Use when TypeScript can infer the type. Use when code feels cluttered with types.
Use when defining array-like types. Use when tempted to use number as index type. Use when understanding array keys.
Use when functions have multiple parameters of same type. Use when parameter order is easy to confuse. Use when designing function signatures.
| name | async-over-callbacks |
| description | Use when writing asynchronous code. Use when tempted to use callbacks. Use when composing multiple async operations. |
Prefer async/await over callbacks for cleaner code and better type flow.
Callbacks create nested, hard-to-follow code. Promises and async/await flatten the structure, make types flow naturally, and enable better error handling.
ALWAYS prefer async/await over callbacks for new code.
Remember:
If you see nested callbacks (the "pyramid of doom"), refactor to 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!)
// ✅ 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 */ }
});
// ✅ 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
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]
}
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
});
// ✅ 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
}
}
// ❌ 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();
}
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;
}
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);
}
function fetchURLAsync(url: string): Promise<string> {
return new Promise((resolve, reject) => {
fetchURL(url, (response, error) => {
if (error) {
reject(error);
} else {
resolve(response);
}
});
});
}
// 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
Pressure: "Promises have overhead, callbacks are more performant"
Response: The overhead is negligible. Code clarity and type safety matter more.
Action: Use async/await. Profile if you suspect performance issues.
Pressure: "This library uses callbacks, we have to use them too"
Response: Wrap callback APIs in Promises.
Action: Use promisify or create a Promise wrapper.
Pressure: "Consistency with existing code"
Response: Gradually migrate. New code should use async/await.
Action: Wrap old APIs, write new code with async/await.
| Excuse | Reality |
|---|---|
| "Callbacks are simpler" | async/await is more readable and maintainable |
| "Promise overhead is too high" | Negligible in practice, clarity wins |
| "Our team knows callbacks" | async/await is standard modern JavaScript |
| Pattern | Callbacks | async/await |
|---|---|---|
| Sequential ops | Nested callbacks | Sequential await |
| Concurrent ops | Manual tracking | Promise.all |
| Error handling | Error-first convention | try/catch |
| Type inference | Manual annotations | Automatic flow |
| Composition | Difficult | Natural |
async/await produces cleaner code with better type inference.
Callbacks create pyramids of nested code where types don't flow well. Promises and async/await flatten the structure, compose naturally, handle errors with standard try/catch, and let TypeScript infer types automatically. Use async/await for all new async code.
Based on "Effective TypeScript" by Dan Vanderkam, Item 27: Use async Functions Instead of Callbacks to Improve Type Flow.