5月30日 02:24
What is the difference between Promise and callback?
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
javascriptfunction 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
javascriptfunction 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
javascriptconst 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
- Node.js core modules: fs, http modules use callbacks
- One-time simple operations: Simple async operations don't need Promise complexity
- 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
- Complex async flows: Multiple async operations need composition
- Need error handling: Need centralized error handling
- Modern JavaScript development: async/await syntax sugar
- 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
| Feature | Callback | Promise |
|---|---|---|
| Code readability | Prone to callback hell | Chaining, flat code |
| Error handling | Scattered, easy to miss | Centralized, easy to manage |
| State management | No clear state | Three clear states |
| Parallel processing | Manual management needed | Promise.all and other methods |
| Composition and reuse | Difficult to compose | Easy to compose |
| Performance | Slightly higher | Slightly lower |
| Memory usage | Lower | Slightly higher |
| Modern support | Traditional approach | Modern standard |
Best Practices
- Prioritize Promises: In modern JavaScript development, prioritize using Promises
- Use async/await: Make async code look like sync code
- Handle errors: Always add error handling
- Avoid nesting: Keep code flat
- Use tools appropriately: Use Promise.all, Promise.race and other methods
- Consider performance: In performance-sensitive scenarios, consider callbacks