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

What is the difference between Promise and callback?

2月22日 14:07

Promises and Callbacks are both ways to handle asynchronous operations in JavaScript, but they have significant differences in design philosophy, usage, and code readability.

Callbacks

Basic Concept

A callback is a function passed as an argument to another function, called after the asynchronous operation completes.

Basic Usage

javascript
function fetchData(callback) { setTimeout(() => { const data = { name: 'John', age: 30 }; callback(null, data); }, 1000); } fetchData((error, data) => { if (error) { console.error('Error:', error); return; } console.log('Data:', data); });

Callback Hell Problem

javascript
// Callback hell: deeply nested code, hard to maintain 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('User posts:', posts); }); }); }); });

Promise

Basic Concept

A Promise is an object representing the eventual completion or failure of an asynchronous operation, providing a more elegant way to handle asynchronous operations.

Basic Usage

javascript
function fetchData() { return new Promise((resolve, reject) => { setTimeout(() => { const data = { name: 'John', age: 30 }; resolve(data); }, 1000); }); } fetchData() .then(data => console.log('Data:', data)) .catch(error => console.error('Error:', error));

Chaining

javascript
// Promise chaining: flat code, easy to read fetch('/api/user') .then(response => response.json()) .then(user => fetch(`/api/posts/${user.id}`)) .then(response => response.json()) .then(posts => console.log('User posts:', posts)) .catch(error => console.error('Error:', error));

Main Differences

1. Code Readability

Callbacks:

javascript
// Deeply nested, hard to read doSomething1((result1) => { doSomething2(result1, (result2) => { doSomething3(result2, (result3) => { doSomething4(result3, (result4) => { console.log(result4); }); }); }); });

Promise:

javascript
// Flat and clear, easy to read doSomething1() .then(result1 => doSomething2(result1)) .then(result2 => doSomething3(result2)) .then(result3 => doSomething4(result3)) .then(result4 => console.log(result4));

2. Error Handling

Callbacks:

javascript
// Error handling scattered, easy to miss function fetchData(callback) { setTimeout(() => { if (Math.random() > 0.5) { callback(new Error('Request failed')); } else { callback(null, { data: 'success' }); } }, 1000); } fetchData((error, data) => { if (error) { console.error('Error:', error); return; } console.log('Data:', data); });

Promise:

javascript
// Error handling centralized, easy to manage function fetchData() { return new Promise((resolve, reject) => { setTimeout(() => { if (Math.random() > 0.5) { reject(new Error('Request failed')); } else { resolve({ data: 'success' }); } }, 1000); }); } fetchData() .then(data => console.log('Data:', data)) .catch(error => console.error('Error:', error));

3. State Management

Callbacks:

  • No clear state concept
  • Callbacks can be called multiple times
  • Difficult to track async operation state

Promise:

  • Three clear states: pending, fulfilled, rejected
  • State change is irreversible
  • Can query Promise state at any time
javascript
const promise = new Promise((resolve) => { setTimeout(() => resolve('Completed'), 1000); }); console.log(promise); // Promise {<pending>} setTimeout(() => { console.log(promise); // Promise {<fulfilled>: 'Completed'} }, 1500);

4. Parallel Processing

Callbacks:

javascript
// Difficult to process multiple async operations in parallel 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
// Easy to process multiple async operations in parallel Promise.all([ fetch('/api/user'), fetch('/api/posts'), fetch('/api/comments') ]) .then(responses => Promise.all(responses.map(r => r.json()))) .then(results => console.log('All data:', results)) .catch(error => console.error('Error:', error));

5. Composition and Reuse

Callbacks:

javascript
// Difficult to compose and reuse function fetchUser(id, callback) { setTimeout(() => callback(null, { id, name: 'John' }), 1000); } function fetchPosts(userId, callback) { setTimeout(() => callback(null, [{ id: 1, title: 'Post 1' }]), 1000); } // Composing requires nesting fetchUser(1, (error, user) => { if (error) return; fetchPosts(user.id, (error, posts) => { if (error) return; console.log({ user, posts }); }); });

Promise:

javascript
// Easy to compose and reuse function fetchUser(id) { return Promise.resolve({ id, name: 'John' }); } function fetchPosts(userId) { return Promise.resolve([{ id: 1, title: 'Post 1' }]); } // Composing is clear fetchUser(1) .then(user => Promise.all([user, fetchPosts(user.id)])) .then(([user, posts]) => console.log({ user, posts }));

Conversion Relationship

Callback to Promise

javascript
// Convert callback to Promise function promisify(fn) { return function(...args) { return new Promise((resolve, reject) => { fn(...args, (error, result) => { if (error) { reject(error); } else { resolve(result); } }); }); }; } // Usage example const readFile = promisify(fs.readFile); readFile('file.txt') .then(data => console.log(data)) .catch(error => console.error(error));

Promise to Callback

javascript
// Convert Promise to callback function callbackify(promiseFn) { return function(...args) { const callback = args.pop(); promiseFn(...args) .then(result => callback(null, result)) .catch(error => callback(error)); }; } // Usage example const fetchDataCallback = callbackify(fetchData); fetchDataCallback((error, data) => { if (error) { console.error(error); return; } console.log(data); });

Performance Comparison

Memory Usage

Callbacks:

  • Lower memory usage
  • No need to create additional Promise objects

Promise:

  • Slightly higher memory usage
  • Each Promise is an object requiring extra memory

Execution Efficiency

Callbacks:

  • Slightly higher execution efficiency
  • No additional Promise object creation and microtask scheduling

Promise:

  • Slightly lower execution efficiency
  • Needs to create Promise objects and schedule microtasks

Actual Impact

javascript
// Callbacks 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');

Use Cases

Scenarios Suitable for Callbacks

  1. Node.js core modules: fs, http modules use callbacks
  2. One-time simple operations: Simple async operations don't need Promise complexity
  3. Performance-sensitive scenarios: Scenarios requiring extreme performance
javascript
// Node.js file reading fs.readFile('file.txt', 'utf8', (error, data) => { if (error) { console.error(error); return; } console.log(data); });

Scenarios Suitable for Promises

  1. Complex async flows: Multiple async operations need composition
  2. Need error handling: Need centralized error handling
  3. Modern JavaScript development: async/await syntax sugar
  4. Frontend development: fetch API, modern browser APIs
javascript
// Using async/await async function fetchData() { try { const user = await fetchUser(); const posts = await fetchPosts(user.id); return { user, posts }; } catch (error) { console.error('Error:', error); throw error; } }

Summary

FeatureCallbackPromise
Code readabilityProne to callback hellChaining, flat code
Error handlingScattered, easy to missCentralized, easy to manage
State managementNo clear stateThree clear states
Parallel processingManual management neededPromise.all and other methods
Composition and reuseDifficult to composeEasy to compose
PerformanceSlightly higherSlightly lower
Memory usageLowerSlightly higher
Modern supportTraditional approachModern standard

Best Practices

  1. Prioritize Promises: In modern JavaScript development, prioritize using Promises
  2. Use async/await: Make async code look like sync code
  3. Handle errors: Always add error handling
  4. Avoid nesting: Keep code flat
  5. Use tools appropriately: Use Promise.all, Promise.race and other methods
  6. Consider performance: In performance-sensitive scenarios, consider callbacks
标签:Promise