Promise 和回调函数的区别是什么?
Promise 和回调函数(Callback)都是 JavaScript 中处理异步操作的方式,但它们在设计理念、使用方式和代码可读性上有显著差异。回调函数基本概念回调函数是将一个函数作为参数传递给另一个函数,在异步操作完成后被调用。基本用法function fetchData(callback) { setTimeout(() => { const data = { name: 'John', age: 30 }; callback(null, data); }, 1000);}fetchData((error, data) => { if (error) { console.error('出错了:', error); return; } console.log('数据:', data);});回调地狱问题// 回调地狱:代码嵌套过深,难以维护fetch('/api/user', (error, userResponse) => { if (error) { console.error(error); return; } userResponse.json((error, user) => { if (error) { console.error(error); return; } fetch(`/api/posts/${user.id}`, (error, postsResponse) => { if (error) { console.error(error); return; } postsResponse.json((error, posts) => { if (error) { console.error(error); return; } console.log('用户文章:', posts); }); }); });});Promise基本概念Promise 是一个代表异步操作最终完成或失败的对象,提供了一种更优雅的方式来处理异步操作。基本用法function fetchData() { return new Promise((resolve, reject) => { setTimeout(() => { const data = { name: 'John', age: 30 }; resolve(data); }, 1000); });}fetchData() .then(data => console.log('数据:', data)) .catch(error => console.error('出错了:', error));链式调用// Promise 链式调用:代码扁平,易于阅读fetch('/api/user') .then(response => response.json()) .then(user => fetch(`/api/posts/${user.id}`)) .then(response => response.json()) .then(posts => console.log('用户文章:', posts)) .catch(error => console.error('出错了:', error));主要区别1. 代码可读性回调函数:// 嵌套过深,难以阅读doSomething1((result1) => { doSomething2(result1, (result2) => { doSomething3(result2, (result3) => { doSomething4(result3, (result4) => { console.log(result4); }); }); });});Promise:// 扁平清晰,易于阅读doSomething1() .then(result1 => doSomething2(result1)) .then(result2 => doSomething3(result2)) .then(result3 => doSomething4(result3)) .then(result4 => console.log(result4));2. 错误处理回调函数:// 错误处理分散,容易遗漏function fetchData(callback) { setTimeout(() => { if (Math.random() > 0.5) { callback(new Error('请求失败')); } else { callback(null, { data: 'success' }); } }, 1000);}fetchData((error, data) => { if (error) { console.error('错误:', error); return; } console.log('数据:', data);});Promise:// 错误处理集中,易于管理function fetchData() { return new Promise((resolve, reject) => { setTimeout(() => { if (Math.random() > 0.5) { reject(new Error('请求失败')); } else { resolve({ data: 'success' }); } }, 1000); });}fetchData() .then(data => console.log('数据:', data)) .catch(error => console.error('错误:', error));3. 状态管理回调函数:没有明确的状态概念回调可能被多次调用难以追踪异步操作的状态Promise:有明确的三种状态:pending、fulfilled、rejected状态一旦改变就不可逆可以随时查询 Promise 的状态const promise = new Promise((resolve) => { setTimeout(() => resolve('完成'), 1000);});console.log(promise); // Promise {<pending>}setTimeout(() => { console.log(promise); // Promise {<fulfilled>: '完成'}}, 1500);4. 并行处理回调函数:// 难以并行处理多个异步操作function fetchAllData(callback) { let completed = 0; const results = []; const urls = ['/api/user', '/api/posts', '/api/comments']; urls.forEach((url, index) => { fetch(url, (error, data) => { if (error) { callback(error); return; } results[index] = data; completed++; if (completed === urls.length) { callback(null, results); } }); });}Promise:// 轻松并行处理多个异步操作Promise.all([ fetch('/api/user'), fetch('/api/posts'), fetch('/api/comments')]) .then(responses => Promise.all(responses.map(r => r.json()))) .then(results => console.log('所有数据:', results)) .catch(error => console.error('错误:', error));5. 组合和复用回调函数:// 难以组合和复用function fetchUser(id, callback) { setTimeout(() => callback(null, { id, name: 'John' }), 1000);}function fetchPosts(userId, callback) { setTimeout(() => callback(null, [{ id: 1, title: 'Post 1' }]), 1000);}// 组合使用需要嵌套fetchUser(1, (error, user) => { if (error) return; fetchPosts(user.id, (error, posts) => { if (error) return; console.log({ user, posts }); });});Promise:// 易于组合和复用function fetchUser(id) { return Promise.resolve({ id, name: 'John' });}function fetchPosts(userId) { return Promise.resolve([{ id: 1, title: 'Post 1' }]);}// 组合使用清晰明了fetchUser(1) .then(user => Promise.all([user, fetchPosts(user.id)])) .then(([user, posts]) => console.log({ user, posts }));转换关系回转 Promise// 将回调函数转换为 Promisefunction promisify(fn) { return function(...args) { return new Promise((resolve, reject) => { fn(...args, (error, result) => { if (error) { reject(error); } else { resolve(result); } }); }); };}// 使用示例const readFile = promisify(fs.readFile);readFile('file.txt') .then(data => console.log(data)) .catch(error => console.error(error));Promise 转回调// 将 Promise 转换为回调函数function callbackify(promiseFn) { return function(...args) { const callback = args.pop(); promiseFn(...args) .then(result => callback(null, result)) .catch(error => callback(error)); };}// 使用示例const fetchDataCallback = callbackify(fetchData);fetchDataCallback((error, data) => { if (error) { console.error(error); return; } console.log(data);});性能对比内存占用回调函数:内存占用较小不需要创建额外的 Promise 对象Promise:内存占用略高每个 Promise 都是一个对象,需要额外的内存执行效率回调函数:执行效率略高没有额外的 Promise 对象创建和微任务调度Promise:执行效率略低需要创建 Promise 对象和调度微任务实际影响// 回调函数console.time('callback');for (let i = 0; i < 10000; i++) { setTimeout(() => {}, 0);}console.timeEnd('callback');// Promiseconsole.time('promise');for (let i = 0; i < 10000; i++) { Promise.resolve();}console.timeEnd('promise');使用场景适合使用回调函数的场景Node.js 核心模块:fs、http 等模块使用回调函数一次性简单操作:简单的异步操作不需要 Promise 的复杂性性能敏感场景:需要极致性能的场景// Node.js 文件读取fs.readFile('file.txt', 'utf8', (error, data) => { if (error) { console.error(error); return; } console.log(data);});适合使用 Promise 的场景复杂的异步流程:多个异步操作需要组合需要错误处理:需要集中处理错误现代 JavaScript 开发:async/await 语法糖前端开发:fetch API、现代浏览器 API// 使用 async/awaitasync function fetchData() { try { const user = await fetchUser(); const posts = await fetchPosts(user.id); return { user, posts }; } catch (error) { console.error('错误:', error); throw error; }}总结| 特性 | 回调函数 | Promise ||------|----------|---------|| 代码可读性 | 容易产生回调地狱 | 链式调用,代码扁平 || 错误处理 | 分散,容易遗漏 | 集中,易于管理 || 状态管理 | 无明确状态 | 三种明确状态 || 并行处理 | 需要手动管理 | Promise.all 等方法 || 组合复用 | 难以组合 | 易于组合 || 性能 | 略高 | 略低 || 内存占用 | 较小 | 略高 || 现代支持 | 传统方式 | 现代标准 |最佳实践优先使用 Promise:在现代 JavaScript 开发中,优先使用 Promise使用 async/await:让异步代码更像同步代码处理错误:总是添加错误处理避免嵌套:保持代码扁平合理使用工具:使用 Promise.all、Promise.race 等方法考虑性能:在性能敏感的场景,可以考虑回调函数