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

How to understand Promise chaining?

2月22日 14:07

Promise chaining is one of the most powerful features of Promise, allowing us to handle multiple asynchronous operations elegantly, avoiding the callback hell problem.

Basic Concepts

Promise chaining refers to the ability to continuously call multiple .then() methods because each .then() method returns a new Promise. Each .then() method receives the result of the previous Promise as a parameter and returns a new Promise.

How Promise Chaining Works

Core Mechanism

  1. .then() method always returns a new Promise
  2. The return value of the previous .then() is passed to the next .then()
  3. If a Promise is returned, it waits for completion before passing the result
  4. Errors propagate down the chain until caught by .catch()

Example Code

javascript
fetch('/api/user') .then(response => response.json()) .then(user => { console.log('User info:', user); return fetch(`/api/posts/${user.id}`); }) .then(response => response.json()) .then(posts => { console.log('User posts:', posts); return posts; }) .catch(error => { console.error('Error occurred:', error); });

Handling Return Values in Chaining

1. Returning a Regular Value

javascript
Promise.resolve(1) .then(value => value + 1) .then(value => value * 2) .then(value => console.log(value)); // Output: 4

2. Returning a Promise

javascript
Promise.resolve(1) .then(value => { return Promise.resolve(value + 1); }) .then(value => { return new Promise(resolve => { setTimeout(() => resolve(value * 2), 1000); }); }) .then(value => console.log(value)); // Output: 4

3. Not Returning a Value (Returns undefined)

javascript
Promise.resolve(1) .then(value => { console.log(value); // Output: 1 // No return, equivalent to returning undefined }) .then(value => console.log(value)); // Output: undefined

4. Throwing an Error

javascript
Promise.resolve(1) .then(value => { throw new Error('Error occurred'); }) .catch(error => { console.error(error.message); // Output: Error occurred return 'Recovered value'; }) .then(value => console.log(value)); // Output: Recovered value

Error Handling Mechanism

Error Propagation

javascript
Promise.resolve() .then(() => { throw new Error('First error'); }) .then(() => { console.log('This line will not execute'); }) .catch(error => { console.error('Caught error:', error.message); return 'Continue execution'; }) .then(value => { console.log(value); // Output: Continue execution });

Multiple Catches

javascript
Promise.resolve() .then(() => { throw new Error('Error'); }) .catch(error => { console.error('First catch:', error.message); throw new Error('New error'); }) .catch(error => { console.error('Second catch:', error.message); });

Promise Chaining vs Callback Hell

Callback Hell (Not Recommended)

javascript
fetch('/api/user', (error, response) => { if (error) { console.error(error); return; } response.json((error, user) => { if (error) { console.error(error); return; } fetch(`/api/posts/${user.id}`, (error, response) => { if (error) { console.error(error); return; } response.json((error, posts) => { if (error) { console.error(error); return; } console.log(posts); }); }); }); });

Promise Chaining (Recommended)

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

Best Practices

1. Keep Chains Flat

javascript
// Not recommended: nested Promises Promise.resolve() .then(() => { return Promise.resolve().then(() => { return Promise.resolve().then(() => { console.log('Too deeply nested'); }); }); }); // Recommended: flat chaining Promise.resolve() .then(() => {}) .then(() => {}) .then(() => console.log('Flat and clear'));

2. Use finally Appropriately

javascript
Promise.resolve() .then(() => console.log('Execute operation')) .catch(error => console.error(error)) .finally(() => console.log('Clean up resources'));

3. Comprehensive Error Handling

javascript
Promise.resolve() .then(data => { // Process data return processData(data); }) .catch(error => { // Handle error console.error('Processing failed:', error); // Can return default value or rethrow error return defaultValue; });

4. Avoid Creating Unnecessary Promises in then

javascript
// Not recommended Promise.resolve(1) .then(value => { return new Promise(resolve => { resolve(value + 1); }); }); // Recommended Promise.resolve(1) .then(value => value + 1);

Common Questions

1. How to Pass Multiple Values in a Chain?

javascript
Promise.all([promise1, promise2]) .then(([result1, result2]) => { console.log(result1, result2); });

2. How to Skip Certain Steps in a Chain?

javascript
Promise.resolve() .then(() => { if (shouldSkip) { return Promise.reject('skip'); } return doSomething(); }) .catch(error => { if (error === 'skip') { return 'Skipped result'; } throw error; }) .then(result => console.log(result));

3. How to Add Logging in a Chain?

javascript
Promise.resolve(1) .tap(value => console.log('Current value:', value)) .then(value => value + 1) .tap(value => console.log('New value:', value));

Comparison with async/await

Promise Chaining

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

async/await (More Readable)

javascript
async function fetchPosts() { try { const userResponse = await fetch('/api/user'); const user = await userResponse.json(); const postsResponse = await fetch(`/api/posts/${user.id}`); const posts = await postsResponse.json(); console.log(posts); } catch (error) { console.error(error); } }

async/await is essentially syntactic sugar for Promise chaining, making asynchronous code look more like synchronous code, improving code readability.

标签:Promise