乐闻世界logo
搜索文章和话题

Promise 和回调函数的区别是什么?

2月22日 14:07

Promise 和回调函数(Callback)都是 JavaScript 中处理异步操作的方式,但它们在设计理念、使用方式和代码可读性上有显著差异。

回调函数

基本概念

回调函数是将一个函数作为参数传递给另一个函数,在异步操作完成后被调用。

基本用法

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); });

回调地狱问题

javascript
// 回调地狱:代码嵌套过深,难以维护 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 是一个代表异步操作最终完成或失败的对象,提供了一种更优雅的方式来处理异步操作。

基本用法

javascript
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));

链式调用

javascript
// 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. 代码可读性

回调函数:

javascript
// 嵌套过深,难以阅读 doSomething1((result1) => { doSomething2(result1, (result2) => { doSomething3(result2, (result3) => { doSomething4(result3, (result4) => { console.log(result4); }); }); }); });

Promise:

javascript
// 扁平清晰,易于阅读 doSomething1() .then(result1 => doSomething2(result1)) .then(result2 => doSomething3(result2)) .then(result3 => doSomething4(result3)) .then(result4 => console.log(result4));

2. 错误处理

回调函数:

javascript
// 错误处理分散,容易遗漏 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:

javascript
// 错误处理集中,易于管理 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 的状态
javascript
const promise = new Promise((resolve) => { setTimeout(() => resolve('完成'), 1000); }); console.log(promise); // Promise {<pending>} setTimeout(() => { console.log(promise); // Promise {<fulfilled>: '完成'} }, 1500);

4. 并行处理

回调函数:

javascript
// 难以并行处理多个异步操作 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:

javascript
// 轻松并行处理多个异步操作 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. 组合和复用

回调函数:

javascript
// 难以组合和复用 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:

javascript
// 易于组合和复用 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

javascript
// 将回调函数转换为 Promise function 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 转回调

javascript
// 将 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 对象和调度微任务

实际影响

javascript
// 回调函数 console.time('callback'); for (let i = 0; i < 10000; i++) { setTimeout(() => {}, 0); } console.timeEnd('callback'); // Promise console.time('promise'); for (let i = 0; i < 10000; i++) { Promise.resolve(); } console.timeEnd('promise');

使用场景

适合使用回调函数的场景

  1. Node.js 核心模块:fs、http 等模块使用回调函数
  2. 一次性简单操作:简单的异步操作不需要 Promise 的复杂性
  3. 性能敏感场景:需要极致性能的场景
javascript
// Node.js 文件读取 fs.readFile('file.txt', 'utf8', (error, data) => { if (error) { console.error(error); return; } console.log(data); });

适合使用 Promise 的场景

  1. 复杂的异步流程:多个异步操作需要组合
  2. 需要错误处理:需要集中处理错误
  3. 现代 JavaScript 开发:async/await 语法糖
  4. 前端开发:fetch API、现代浏览器 API
javascript
// 使用 async/await async 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 等方法
组合复用难以组合易于组合
性能略高略低
内存占用较小略高
现代支持传统方式现代标准

最佳实践

  1. 优先使用 Promise:在现代 JavaScript 开发中,优先使用 Promise
  2. 使用 async/await:让异步代码更像同步代码
  3. 处理错误:总是添加错误处理
  4. 避免嵌套:保持代码扁平
  5. 合理使用工具:使用 Promise.all、Promise.race 等方法
  6. 考虑性能:在性能敏感的场景,可以考虑回调函数
标签:Promise