Promise error handling mechanism
Basic Concepts of Promise Error Handling
The Promise object introduced in ECMAScript 6 provides a more elegant way to handle errors in asynchronous programming. The error handling mechanism of Promises is primarily implemented through the second parameter of the then
method and the catch
method, solving the chaotic error handling issues in traditional callback functions.
const promise = new Promise((resolve, reject) => {
// Asynchronous operation
setTimeout(() => {
const random = Math.random();
if (random > 0.5) {
resolve('Success');
} else {
reject(new Error('Failure'));
}
}, 1000);
});
promise
.then(
result => console.log(result),
error => console.error(error.message) // Error handling
);
Error Handling in the then
Method
The then
method accepts two parameters: the first is the callback function when the Promise succeeds, and the second is the callback function when the Promise fails. This design allows error handling to be directly associated with the corresponding asynchronous operation.
fetch('https://api.example.com/data')
.then(
response => {
if (!response.ok) {
throw new Error('Network response was not OK');
}
return response.json();
},
error => {
console.error('Request failed:', error);
return Promise.reject(error);
}
)
.then(data => console.log(data));
Using the catch
Method
The catch
method is syntactic sugar for then(null, rejection)
and is specifically used to catch errors in the Promise chain that have not been handled. It can catch all unhandled errors from preceding Promises.
someAsyncOperation()
.then(result => {
// Process the result
return processResult(result);
})
.then(processedResult => {
// Further processing
return saveResult(processedResult);
})
.catch(error => {
console.error('Error in the operation chain:', error);
// Can return a default value or rethrow the error
return defaultResult;
});
Error Propagation in Promise Chains
Errors in a Promise chain propagate downward until they encounter an error handling function. If there is no error handling function, the error will be silently ignored (in browsers, an uncaught error may be displayed in the console).
Promise.resolve()
.then(() => {
throw new Error('First error');
})
.then(() => {
console.log('This then will not execute');
})
.catch(error => {
console.error('Caught error:', error.message);
throw new Error('New error thrown from catch');
})
.catch(error => {
console.error('Caught second error:', error.message);
});
The finally
Method
The finally
method executes regardless of the final state of the Promise, making it suitable for cleanup tasks. It does not receive any parameters and cannot determine whether the Promise succeeded or failed.
let isLoading = true;
fetchData()
.then(data => {
// Process data
})
.catch(error => {
// Handle error
})
.finally(() => {
isLoading = false;
console.log('Request completed, regardless of success or failure');
});
Uncaught Promise Errors
Uncaught Promise errors will not stop script execution but may display warnings in the console. In modern JavaScript environments, the unhandledrejection
event can be used to globally catch these errors.
window.addEventListener('unhandledrejection', event => {
console.warn('Unhandled Promise rejection:', event.reason);
event.preventDefault(); // Prevent default behavior (console error)
});
// Uncaught Promise rejection
Promise.reject(new Error('This error will be caught globally'));
Error Handling in Promise.all
When using Promise.all
to handle multiple Promises, if one Promise is rejected, the entire Promise.all
will be immediately rejected. To handle errors for each Promise individually, you can add error handling to each Promise in the array passed to Promise.all
.
const promises = [
fetch('/api/data1').catch(e => ({ error: e.message })),
fetch('/api/data2').catch(e => ({ error: e.message })),
fetch('/api/data3').catch(e => ({ error: e.message }))
];
Promise.all(promises)
.then(results => {
results.forEach(result => {
if (result.error) {
console.log('Request failed:', result.error);
} else {
console.log('Request succeeded:', result);
}
});
});
Error Handling in Promise.race
Promise.race
returns the first settled Promise (whether fulfilled or rejected). Its error handling requires attention because the first rejected Promise will cause the entire Promise.race
to be rejected.
const timeout = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Request timeout')), 5000);
});
Promise.race([
fetch('/api/data'),
timeout
])
.then(data => console.log('Data fetched successfully:', data))
.catch(error => console.error('Error:', error.message));
Error Handling in async/await
Async functions return a Promise, and try/catch
can be used to handle errors, making asynchronous code error handling appear synchronous.
async function fetchData() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error('Network response was not OK');
}
const data = await response.json();
console.log('Fetched data:', data);
return data;
} catch (error) {
console.error('Failed to fetch data:', error);
// Can choose to rethrow the error or return a default value
throw error;
}
}
// Calling the async function
fetchData().catch(e => console.error('External catch:', e));
Error Boundary Pattern
In complex Promise chains, the error boundary pattern can be used to limit the scope of error propagation, preventing one error from affecting the entire application.
function withErrorBoundary(promise, fallback) {
return promise.catch(error => {
console.error('Error boundary caught:', error);
return fallback;
});
}
// Using the error boundary
withErrorBoundary(
riskyOperation(),
{ default: 'value' }
)
.then(result => {
// Can safely use result here
console.log('Result:', result);
});
Custom Error Types
Creating custom error types for different error scenarios allows for more precise identification and handling of errors.
class NetworkError extends Error {
constructor(message) {
super(message);
this.name = 'NetworkError';
}
}
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = 'ValidationError';
}
}
async function validateAndFetch() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new NetworkError('Network request failed');
}
const data = await response.json();
if (!data.valid) {
throw new ValidationError('Data validation failed');
}
return data;
} catch (error) {
if (error instanceof NetworkError) {
console.error('Network issue:', error.message);
// Retry logic
} else if (error instanceof ValidationError) {
console.error('Data issue:', error.message);
// Display user-friendly error
}
throw error;
}
}
Best Practices for Promise Error Handling
- Always Return a Promise: In
then
andcatch
callbacks, either return a value or return a Promise to ensure the chain can continue.
// Bad practice - breaks the Promise chain
promise.then(result => {
saveResult(result); // No return
});
// Good practice
promise.then(result => {
return saveResult(result); // Explicit return
});
- Avoid Nested Promises: Use chaining instead of nesting to improve code readability.
// Avoid this
getUser().then(user => {
getPosts(user.id).then(posts => {
// Process posts
});
});
// Do this instead
getUser()
.then(user => getPosts(user.id))
.then(posts => {
// Process posts
});
- Use
catch
Appropriately: Usecatch
at appropriate points in the Promise chain, not after everythen
.
// Unnecessarily adding catch after each then
operation1()
.then(result1 => { /*...*/ })
.catch(e => console.error(e))
.then(result2 => { /*...*/ })
.catch(e => console.error(e));
// Better approach - handle uniformly at the end of the chain
operation1()
.then(result1 => { /*...*/ })
.then(result2 => { /*...*/ })
.catch(e => console.error(e));
Common Pitfalls in Error Handling
- Forgetting to Return a Promise: Starting a new asynchronous operation in a
then
callback but forgetting to return the Promise can cause subsequentthen
callbacks to execute before the new operation completes.
// Incorrect example
getUser()
.then(user => {
fetchPosts(user.id); // Forgot to return
})
.then(posts => {
// posts will be undefined
console.log(posts);
});
// Correct approach
getUser()
.then(user => {
return fetchPosts(user.id); // Explicit return
})
.then(posts => {
console.log(posts);
});
- Swallowing Errors: Not rethrowing the error or returning a rejected Promise in a
catch
callback can cause the error to be "swallowed."
// Error is swallowed
dangerousOperation()
.catch(error => {
console.error(error);
// No rethrow
})
.then(() => {
// This will execute as if no error occurred
});
// Correct approach - rethrow the error
dangerousOperation()
.catch(error => {
console.error(error);
throw error; // Or return Promise.reject(error)
});
- Confusing Synchronous and Asynchronous Errors: In a Promise constructor, synchronous errors are automatically caught and converted to a rejected Promise, but synchronous errors in
then
callbacks need to be manually caught.
// Synchronous errors in Promise constructor are automatically caught
new Promise(() => {
throw new Error('Synchronous error');
}).catch(e => console.error(e)); // Will be caught
// Synchronous errors in then callbacks are also caught
Promise.resolve()
.then(() => {
throw new Error('Synchronous error in then');
})
.catch(e => console.error(e)); // Will be caught
// But not in regular functions
function riskyFunction() {
throw new Error('Regular function error');
}
try {
riskyFunction();
} catch (e) {
console.error(e);
}
Advanced Error Handling Patterns
- Retry Mechanism: Implement a Promise wrapper with retry logic.
function retry(promiseFactory, retries = 3, delay = 1000) {
return new Promise((resolve, reject) => {
const attempt = (n) => {
promiseFactory()
.then(resolve)
.catch(error => {
if (n <= 1) {
reject(error);
} else {
console.log(`Attempt failed, ${n - 1} retries left...`);
setTimeout(() => attempt(n - 1), delay);
}
});
};
attempt(retries);
});
}
// Usage example
retry(() => fetch('/api/unstable'), 5, 2000)
.then(response => console.log('Final success:', response))
.catch(error => console.error('All attempts failed:', error));
- Timeout Control: Add timeout functionality to a Promise.
function withTimeout(promise, timeoutMs, timeoutError = new Error('Operation timed out')) {
let timeoutId;
const timeoutPromise = new Promise((_, reject) => {
timeoutId = setTimeout(() => reject(timeoutError), timeoutMs);
});
return Promise.race([promise, timeoutPromise]).finally(() => {
clearTimeout(timeoutId);
});
}
// Usage example
withTimeout(fetch('/api/slow'), 3000)
.then(response => console.log('Response:', response))
.catch(error => console.error('Error:', error.message));
- Error Aggregation: When handling multiple independent operations, collect all errors rather than just the first one.
function settleAll(promises) {
return Promise.all(promises.map(p =>
p.then(
value => ({ status: 'fulfilled', value }),
reason => ({ status: 'rejected', reason })
)
));
}
// Usage example
settleAll([
Promise.resolve('Success1'),
Promise.reject('Failure1'),
Promise.resolve('Success2'),
Promise.reject('Failure2')
]).then(results => {
const successes = results.filter(r => r.status === 'fulfilled');
const failures = results.filter(r => r.status === 'rejected');
console.log('Successes:', successes);
console.log('Failures:', failures);
});
Promise Error Handling and the Event Loop
Understanding the relationship between Promise error handling and the JavaScript event loop is important. Promise callbacks always execute as microtasks, meaning error handling also executes in the microtask queue.
console.log('Script start');
// Synchronous errors are thrown immediately
// throw new Error('Synchronous error'); // This would stop script execution
// Asynchronous Promise error
Promise.reject(new Error('Asynchronous error')).catch(e => {
console.log('Caught asynchronous error:', e.message);
});
setTimeout(() => {
console.log('setTimeout callback');
throw new Error('Error in setTimeout'); // This will bubble to the global scope
}, 0);
Promise.resolve().then(() => {
console.log('Microtask 1');
throw new Error('Error in microtask');
}).catch(e => {
console.log('Caught microtask error:', e.message);
});
console.log('Script end');
/*
Output order:
Script start
Script end
Microtask 1
Caught microtask error: Error in microtask
Caught asynchronous error: Asynchronous error
setTimeout callback
(Then the error in setTimeout will bubble to the global scope)
*/
Promise Error Handling in Node.js
In Node.js environments, Promise error handling has some special considerations, especially when interoperating with EventEmitter and callback APIs.
const fs = require('fs').promises;
const util = require('util');
const stream = require('stream');
// Convert callback-style API to Promise
const readFile = util.promisify(fs.readFile);
// Handle multiple potentially failing IO operations
async function processFiles(filePaths) {
const results = [];
for (const filePath of filePaths) {
try {
const content = await readFile(filePath, 'utf8');
results.push({ filePath, content, status: 'success' });
} catch (error) {
results.push({ filePath, error: error.message, status: 'failed' });
// Can choose to continue processing other files rather than exiting immediately
}
}
return results;
}
// Handle Promise errors in streams
async function handleStreamWithPromise(stream) {
const pipeline = util.promisify(stream.pipeline);
try {
await pipeline(
stream,
async function* (source) {
for await (const chunk of source) {
yield processChunk(chunk); // Assume processChunk might throw an error
}
},
someWritableStream
);
} catch (error) {
console.error('Stream processing failed:', error);
// Clean up resources
}
}
Browser vs. Node.js Environment Differences
Different JavaScript environments handle uncaught Promise errors differently:
-
Browser Environment:
- Uncaught Promise rejections trigger the
unhandledrejection
event. - Modern browsers display warnings in the console.
- Does not cause the script to completely stop.
- Uncaught Promise rejections trigger the
-
Node.js Environment:
- Triggers the
unhandledRejection
event. - Starting from Node.js 15, unhandled Promise rejections cause the process to exit (can be disabled via flag).
- Can use
process.on('unhandledRejection')
for global catching.
- Triggers the
// Global catching in browsers
window.addEventListener('unhandledrejection', event => {
console.error('Unhandled Promise rejection:', event.reason);
// event.preventDefault(); // Can prevent the default console.error
});
// Global catching in Node.js
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled rejection:', reason);
// Application-specific logging or cleanup
});
// Detection applicable in both environments
let potentiallyUnhandledRejections = new Map();
window.addEventListener('unhandledrejection', event => {
potentiallyUnhandledRejections.set(event.promise, event.reason);
});
window.addEventListener('rejectionhandled', event => {
potentiallyUnhandledRejections.delete(event.promise);
});
setInterval(() => {
potentiallyUnhandledRejections.forEach((reason, promise) => {
console.error('Potentially unhandled rejection:', reason);
// Handle these rejections
});
potentiallyUnhandledRejections.clear();
}, 60000);
Promise Error Handling with TypeScript
When using Promises in TypeScript, the type system can help handle errors more safely.
interface User {
id: number;
name: string;
}
interface ApiError {
message: string;
statusCode: number;
}
async function fetchUser(userId: number): Promise<User> {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
const error: ApiError = {
message: `HTTP error ${response.status}`,
statusCode: response.status
};
throw error;
}
return await response.json() as User;
} catch (error) {
console.error('Failed to fetch user:', error instanceof Error ? error.message : error);
throw error; // Rethrow to maintain type safety
}
}
// Usage
fetchUser(123)
.then(user => console.log('User:', user))
.catch((error: ApiError | Error) => {
if ('statusCode' in error) {
console.error('API error:', error.statusCode, error.message);
} else {
console.error('General error:', error.message);
}
});
Promise Error Handling and Functional Programming
Combining functional programming concepts, more generic error handling utility functions can be created.
// Higher-order function: Wraps a function to handle errors in the returned Promise in a specific
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:Promise.race()方法
下一篇:Promise与回调地狱的解决