The Promise pattern handles asynchronous operations.
The Promise pattern is a powerful tool in JavaScript for handling asynchronous operations. It simplifies the callback hell problem through chaining and state management. Whether it's network requests, file reading, or timed tasks, Promises provide a clearer way to organize code logic.
Basic Concepts of Promises
A Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value. It has three states:
- pending: initial state
- fulfilled: operation completed successfully
- rejected: operation failed
const promise = new Promise((resolve, reject) => {
// Asynchronous operation
setTimeout(() => {
const success = true;
if (success) {
resolve('Operation succeeded');
} else {
reject('Operation failed');
}
}, 1000);
});
Chaining Promises
The true power of Promises lies in their ability to chain calls, avoiding callback hell:
fetchData()
.then(processData)
.then(saveData)
.catch(handleError);
Each .then()
returns a new Promise, allowing continued chaining. .catch()
catches errors occurring anywhere in the chain.
Static Methods of Promises
Promises provide several useful static methods:
Promise.all
Waits for all Promises to complete or the first Promise to reject:
Promise.all([
fetch('/api/users'),
fetch('/api/posts')
]).then(([users, posts]) => {
console.log('All data loaded');
}).catch(err => {
console.error('One request failed', err);
});
Promise.race
Returns the first Promise to complete (whether resolved or rejected):
Promise.race([
fetch('/api/data'),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Request timeout')), 5000)
)
]).then(data => {
console.log('Data fetched successfully');
}).catch(err => {
console.error('Request timed out or failed', err);
});
Promise.allSettled
Waits for all Promises to complete (regardless of success or failure):
Promise.allSettled([
Promise.resolve('Success'),
Promise.reject('Failure')
]).then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('Success:', result.value);
} else {
console.log('Failure:', result.reason);
}
});
});
Error Handling in Promises
Promises offer multiple ways to handle errors:
// Method 1: Using catch
somePromise()
.then(handleSuccess)
.catch(handleError);
// Method 2: Second parameter of then
somePromise()
.then(handleSuccess, handleError);
// Method 3: Global error handling
window.addEventListener('unhandledrejection', event => {
console.warn('Unhandled Promise rejection:', event.reason);
});
Promises and async/await
async/await is syntactic sugar built on top of Promises:
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return processData(data);
} catch (error) {
console.error('Failed to fetch data:', error);
throw error;
}
}
Advanced Promise Patterns
Canceling Promises
Although Promises don't natively support cancellation, it can be implemented via wrapping:
function cancellablePromise(promise) {
let isCancelled = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
value => !isCancelled && resolve(value),
error => !isCancelled && reject(error)
);
});
return {
promise: wrappedPromise,
cancel: () => { isCancelled = true; }
};
}
const { promise, cancel } = cancellablePromise(fetch('/api/data'));
// Later, calling cancel() can "cancel" the Promise
Promise Caching
Avoid repeated requests for the same resource:
const promiseCache = new Map();
function getWithCache(url) {
if (promiseCache.has(url)) {
return promiseCache.get(url);
}
const promise = fetch(url)
.then(response => response.json())
.finally(() => promiseCache.delete(url));
promiseCache.set(url, promise);
return promise;
}
Performance Considerations for Promises
While Promises offer many conveniences, performance considerations are important:
- Microtask queue: Promise callbacks execute as microtasks, which may impact performance-sensitive code
- Memory usage: Long-pending Promises retain their closure references
- Error stacks: Error stacks in Promise chains may be incomplete
// Performance test example
console.time('Promise');
Promise.resolve()
.then(() => {})
.then(() => {})
.then(() => {
console.timeEnd('Promise'); // Shows execution time of the Promise chain
});
Common Promise Pitfalls
- Forgetting to return values in Promise chains:
// Bad example
somePromise()
.then(result => {
processResult(result); // Forgot to return
})
.then(processed => {
// processed will be undefined
});
// Good example
somePromise()
.then(result => {
return processResult(result); // Explicit return
});
- Improper error handling:
// Bad example - then after catch
somePromise()
.catch(handleError)
.then(() => {
// This will still execute even if an error occurred
});
// Good example - rethrowing errors
somePromise()
.catch(err => {
handleError(err);
throw err; // Or return a rejected Promise
})
.then(() => {
// Only executes if no error occurred
});
Practical Use Cases for Promises
Sequential Execution of Async Tasks
function executeSequentially(promises) {
return promises.reduce((chain, promise) => {
return chain.then(() => promise);
}, Promise.resolve());
}
executeSequentially([
() => fetch('/api/step1'),
() => fetch('/api/step2'),
() => fetch('/api/step3')
]);
Promise with Retry Mechanism
function retry(fn, retries = 3, delay = 1000) {
return new Promise((resolve, reject) => {
const attempt = (n) => {
fn()
.then(resolve)
.catch(err => {
if (n === 0) {
reject(err);
} else {
setTimeout(() => attempt(n - 1), delay);
}
});
};
attempt(retries);
});
}
retry(() => fetch('/api/unstable'), 5, 2000);
Promises and the Event Loop
Understanding Promise behavior in the event loop is crucial:
console.log('Script start');
setTimeout(() => console.log('setTimeout'), 0);
Promise.resolve()
.then(() => console.log('Promise 1'))
.then(() => console.log('Promise 2'));
console.log('Script end');
// Output order:
// Script start
// Script end
// Promise 1
// Promise 2
// setTimeout
Unit Testing Promises
Testing Promise code requires special handling:
// Using Jest to test Promises
test('fetchData returns expected data', () => {
// Ensure the Promise is returned
return fetchData().then(data => {
expect(data).toEqual(expectedData);
});
});
// Or using async/await
test('fetchData returns expected data', async () => {
const data = await fetchData();
expect(data).toEqual(expectedData);
});
// Testing error cases
test('fetchData handles errors', async () => {
await expect(fetchDataWithError()).rejects.toThrow('Request failed');
});
Browser Compatibility for Promises
While modern browsers support Promises, older environments may require polyfills:
<!-- Including a Promise polyfill -->
<script src="https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js"></script>
Or transpilation via tools like Babel:
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
useBuiltIns: 'usage',
corejs: 3
}]
]
};
Promises and Web Workers
Promises work well with Web Workers:
// main.js
const worker = new Worker('worker.js');
function workerPromise(message) {
return new Promise((resolve, reject) => {
worker.onmessage = e => resolve(e.data);
worker.onerror = e => reject(e.error);
worker.postMessage(message);
});
}
workerPromise('heavy task')
.then(result => console.log('Worker completed:', result));
// worker.js
self.onmessage = function(e) {
const result = doHeavyTask(e.data);
self.postMessage(result);
};
Debugging Techniques for Promises
Debugging Promise chains can be challenging:
- Inserting debug points using
.then
:
somePromise()
.then(data => {
console.log('Debug point 1:', data);
return data;
})
.then(processData)
.then(result => {
console.log('Debug point 2:', result);
return result;
});
- Simplifying debugging with async/await:
async function debugFlow() {
const data = await somePromise();
debugger; // Can inspect data here
const processed = await processData(data);
debugger; // Can inspect processed here
return processed;
}
- Using third-party libraries like
bluebird
for enhanced debugging:
const Promise = require('bluebird');
Promise.config({ longStackTraces: true });
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn