一、回调函数(Callback)
原理:事件循环与任务队列
JavaScript 引擎通过事件循环机制实现异步执行。所有同步代码直接压入调用栈执行,而异步任务(如 setTimeout、I/O 操作)会被推入任务队列,等待主线程空闲后执行。
代码示例:基础用法与回调地狱
// 基础回调:文件读取
const fs = require('fs');
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log('文件内容:', data); // 输出文件内容
});
console.log('读取中...'); // 此行先输出
// 回调地狱示例
fs.readFile('file1.txt', 'utf8', (err1, data1) => {
fs.readFile('file2.txt', 'utf8', (err2, data2) => {
fs.readFile('file3.txt', 'utf8', (err3, data3) => {
console.log(data1, data2, data3);
});
});
});
底层执行流程
-
同步阶段:
- 执行
fs.readFile,将异步任务注册到线程池。 - 输出 "读取中..."。
- 执行
-
异步阶段:
- 文件读取完成后,将回调函数推入任务队列。
- 事件循环检测到主线程空闲,执行回调输出文件内容。
优缺点分析
- ✅ 简单直接,无需复杂语法
- ❌ 多层嵌套导致回调地狱(Callback Hell)
- ❌ 错误处理需要手动传递(遵循"错误优先"原则)
二、事件监听(Event Listener)
原理:观察者模式与微任务队列
事件监听基于观察者模式,通过 on/emit 方法实现解耦。事件触发时,所有注册的监听器会被推入微任务队列,优先于宏任务队列执行。
代码示例:DOM 事件与自定义事件
// DOM 事件监听
document.getElementById('btn').addEventListener('click', () => {
console.log('按钮被点击');
});
// 自定义事件系统
class EventEmitter {
on(event, listener) {
this.listeners = this.listeners || {};
(this.listeners[event] || (this.listeners[event] = [])).push(listener);
}
emit(event, ...args) {
Promise.resolve().then(() => {
(this.listeners[event] || []).forEach(listener => listener(...args));
});
}
}
const emitter = new EventEmitter();
emitter.on('data', (msg) => console.log('收到:', msg));
emitter.emit('data', 'Hello Event'); // 1秒后输出
底层执行流程
-
注册监听器:将
data事件回调存入listeners对象。 -
触发事件:
emit方法将回调函数包装为 Promise,推入微任务队列。- 微任务优先级高于宏任务(如
setTimeout),因此回调会在当前事件循环的微任务阶段执行。
优缺点分析
- ✅ 解耦事件生产者与消费者
- ✅ 支持多个监听器并行执行
- ❌ 需手动管理事件生命周期(如取消监听)
三、发布/订阅模式(Pub/Sub)
原理:中介者模式与消息中心
发布/订阅模式通过一个中介(Broker)管理所有订阅关系,实现完全解耦。订阅者通过主题(Topic)订阅消息,发布者无需知道订阅者存在。
代码示例:实现 Pub/Sub 系统
class PubSub {
constructor() {
this.topics = new Map();
}
subscribe(topic, callback) {
if (!this.topics.has(topic)) this.topics.set(topic, []);
this.topics.get(topic).push(callback);
return () => this.unsubscribe(topic, callback);
}
publish(topic, data) {
Promise.resolve().then(() => {
(this.topics.get(topic) || []).forEach(callback => callback(data));
});
}
unsubscribe(topic, callback) {
const callbacks = this.topics.get(topic);
if (callbacks) {
this.topics.set(topic, callbacks.filter(cb => cb !== callback));
}
}
}
// 使用示例
const pubsub = new PubSub();
const unsubscribe = pubsub.subscribe('news', (msg) => {
console.log('新闻:', msg); // 输出: 突发新闻!
});
pubsub.publish('news', '突发新闻!'); // 1秒后发布
unsubscribe(); // 取消订阅
底层执行流程
-
订阅主题:将回调函数存入
topicsMap 结构。 -
发布消息:
- 通过
Promise.resolve().then()将回调推入微任务队列。 - 微任务执行时,遍历所有订阅者并触发回调。
- 通过
优缺点分析
- ✅ 完全解耦发布者与订阅者
- ✅ 支持动态订阅/取消订阅
- ❌ 需管理主题生命周期,避免内存泄漏
四、Promise:链式调用与状态管理
原理:微任务与状态机
Promise 对象是一个状态机,拥有三种状态:pending(初始)、fulfilled(成功)、rejected(失败)。状态变更后不可逆,且会触发关联的 .then() 或 .catch() 方法。
代码示例:链式调用与错误处理
// 创建 Promise 对象
function fetchData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (url === 'valid') resolve('数据加载成功');
else reject(new Error('无效 URL'));
}, 1000);
});
}
// 链式调用
fetchData('valid')
.then(data => {
console.log(data); // 输出: 数据加载成功
return fetchData('invalid');
})
.then(data => console.log(data))
.catch(err => console.error('错误:', err.message)); // 输出: 无效 URL
// 并行请求
Promise.all([
fetch('https://api.example.com/users'),
fetch('https://api.example.com/posts')
]).then(([users, posts]) => {
console.log('用户:', users, '帖子:', posts);
});
底层执行流程
-
创建 Promise:
- 执行器函数(executor)立即执行,注册异步操作。
- 异步操作完成后调用
resolve或reject变更状态。
-
链式调用:
.then()和.catch()返回新的 Promise,实现链式调用。- 回调函数被推入微任务队列,保证异步顺序。
优缺点分析
- ✅ 避免回调地狱,代码更扁平
- ✅ 统一错误处理(
.catch()冒泡) - ❌ 需处理未捕获的 Promise 错误(可通过
unhandledrejection事件监听)
五、生成器(Generators)与 yield:协程控制流
原理:协程与暂停执行
生成器通过 function* 定义,使用 yield 关键字暂停执行,将控制权交还给调用者。配合协程库(如 co)可实现同步风格的异步控制流。
代码示例:手动控制生成器
// 定义生成器
function* asyncTask() {
console.log('开始');
const data = yield fetchData('valid'); // 暂停执行
console.log('数据:', data); // 输出: 数据加载成功
}
// 手动执行生成器
const gen = asyncTask();
const next = gen.next(); // 启动生成器,执行到第一个 yield
next.value
.then(data => gen.next(data)) // 恢复执行,传递结果
.catch(err => gen.throw(err)); // 抛出错误到生成器
// 使用 co 库自动执行
const co = require('co');
co(function* () {
const [user, posts] = yield Promise.all([
fetch('/user'),
fetch('/posts')
]);
console.log('用户:', user, '帖子:', posts);
});
底层执行流程
-
创建生成器:
- 执行
gen.next()启动生成器,执行到第一个yield暂停。 - 返回
{value: Promise, done: false}。
- 执行
-
恢复执行:
- 解析
yield后的 Promise,通过gen.next(data)传递结果并恢复执行。
- 解析
优缺点分析
- ✅ 精确控制异步流程(如状态机)
- ❌ 语法复杂,需额外工具(如
co) - ❌ 已被
async/await取代,现代项目不推荐使用
六、async/await:Promise 的语法糖
原理:编译时转换与微任务
async/await 是基于 Promise 的语法糖,通过编译器(如 Babel)转换为 Promise 链式调用。await 关键字会暂停函数执行,直到 Promise 解析。
代码示例:并行请求与错误处理
// 定义异步函数
async function fetchData() {
try {
const [user, posts] = await Promise.all([
fetch('/user'),
fetch('/posts')
]);
console.log('用户:', user);
console.log('帖子:', posts);
} catch (err) {
console.error('请求失败:', err);
}
}
// 调用异步函数
fetchData();
// 顶层 await(ES2022)
(async function () {
const data = await fetch('/api/data');
console.log('顶层 await:', data);
})();
底层执行流程
-
编译转换:
// Babel 转换后的代码(简化) function fetchData() { return Promise.resolve().then(() => { return Promise.all([...]).then(([user, posts]) => { console.log(...); }); }); } -
执行阶段:
await将后续代码包装为 Promise,推入微任务队列。- 错误通过
try/catch捕获,或冒泡到.catch()。
优缺点分析
- ✅ 同步风格代码,提高可读性
- ✅ 自然错误处理(
try/catch) - ❌ 需注意
await位置(避免阻塞主线程)
综合对比与选择建议
| 方法 | 适用场景 | 核心优势 | 推荐指数 |
|---|---|---|---|
| 回调函数 | 简单任务、传统 API | 无需额外依赖 | ★☆☆☆☆ |
| 事件监听 | 用户交互、实时通信 | 天然解耦 | ★★★☆☆ |
| 发布订阅 | 复杂系统、跨组件通信 | 高度灵活,支持多对多 | ★★★★☆ |
| Promise | 网络请求、链式异步 | 解决回调地狱,支持错误冒泡 | ★★★★☆ |
| 生成器 | 精细控制流程(历史方案) | 手动暂停/恢复执行 | ★★☆☆☆ |
async/await | 现代异步代码(推荐) | 同步写法,易于调试 | ★★★★★ |
选择策略
- 简单任务:优先使用
async/await或 Promise。 - 事件驱动:使用事件监听(如 DOM 事件)。
- 跨模块通信:发布订阅模式。
- 遗留代码:回调函数或生成器(需谨慎)。
未来趋势
- 结构化并发:TC39 提案中的
Promise.any、Promise.allSettled。 - 顶层
await:ES2022 支持模块顶层使用await。 - 并发控制:
Promise.all的变种如Promise.allLimit(控制并行数)。
结语
在实际项目中,建议优先采用 async/await + Promise 的组合,结合发布订阅模式处理复杂通信,以实现高效、可维护的异步代码。