Common asynchronous pattern implementations
Basic Implementation of Promise
The Promise introduced in ECMAScript 6 is a core mechanism for handling asynchronous operations. A Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
const promise = new Promise((resolve, reject) => {
// Asynchronous operation
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve('Operation succeeded');
} else {
reject(new Error('Operation failed'));
}
}, 1000);
});
promise
.then(result => console.log(result))
.catch(error => console.error(error));
A Promise has three states:
- pending: initial state
- fulfilled: operation completed successfully
- rejected: operation failed
Promise chaining allows multiple asynchronous operations to be linked together:
fetch('/api/user')
.then(response => response.json())
.then(user => fetch(`/api/posts/${user.id}`))
.then(posts => console.log(posts))
.catch(error => console.error('Request failed:', error));
async/await Syntactic Sugar
async/await is syntactic sugar built on top of Promises, making asynchronous code look like synchronous code.
async function fetchUserAndPosts() {
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('Request failed:', error);
}
}
An async function always returns a Promise:
async function getNumber() {
return 42; // Equivalent to return Promise.resolve(42)
}
getNumber().then(value => console.log(value)); // 42
Generators and Coroutines
Generator functions can pause and resume execution, enabling coroutine-style asynchronous programming with yield
.
function* asyncGenerator() {
const result1 = yield new Promise(resolve =>
setTimeout(() => resolve('Step 1 completed'), 1000)
);
console.log(result1);
const result2 = yield new Promise(resolve =>
setTimeout(() => resolve('Step 2 completed'), 1000)
);
console.log(result2);
}
function runGenerator(gen) {
const iterator = gen();
function iterate({ value, done }) {
if (done) return;
value.then(result => {
iterate(iterator.next(result));
}).catch(error => {
iterator.throw(error);
});
}
iterate(iterator.next());
}
runGenerator(asyncGenerator);
Promise Composition Methods
ES6 provides various Promise composition methods for handling multiple asynchronous operations.
Promise.all
waits for all Promises to complete:
const [user, posts] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/posts').then(r => r.json())
]);
Promise.race
returns the first completed Promise:
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Request timeout')), 5000);
const data = await Promise.race([
fetch('/api/data'),
timeout
]);
Promise.allSettled
waits for all Promises to settle (regardless of success or failure):
const results = await Promise.allSettled([
Promise.resolve('Success'),
Promise.reject('Failure')
]);
/*
[
{ status: 'fulfilled', value: 'Success' },
{ status: 'rejected', reason: 'Failure' }
]
*/
Error Handling Patterns
Error handling in asynchronous code requires special attention.
Error catching in Promise chains:
fetch('/api/data')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not OK');
}
return response.json();
})
.catch(error => {
console.error('Request failed:', error);
return getFallbackData(); // Provide fallback data
});
try/catch with async/await:
async function loadData() {
try {
const response = await fetch('/api/data');
if (!response.ok) throw new Error('Network response was not OK');
return await response.json();
} catch (error) {
console.error('Failed to load data:', error);
return getFallbackData();
}
}
Global unhandled Promise rejections:
window.addEventListener('unhandledrejection', event => {
console.error('Unhandled Promise rejection:', event.reason);
event.preventDefault();
});
Advanced Asynchronous Patterns
More complex asynchronous control patterns can be implemented using Promises.
Promise caching:
function createCachedFetch() {
const cache = new Map();
return async function cachedFetch(url) {
if (cache.has(url)) {
return cache.get(url);
}
const response = await fetch(url);
const data = await response.json();
cache.set(url, data);
return data;
};
}
Promise retry mechanism:
async function retry(fn, retries = 3, delay = 1000) {
try {
return await fn();
} catch (error) {
if (retries <= 0) throw error;
await new Promise(resolve => setTimeout(resolve, delay));
return retry(fn, retries - 1, delay * 2);
}
}
const data = await retry(() => fetch('/api/unstable'));
Promise timeout control:
function withTimeout(promise, timeout) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Operation timeout')), timeout)
)
]);
}
const result = await withTimeout(fetch('/api/slow'), 5000);
Asynchronous Iterators
Asynchronous iterators introduced in ES2018 handle asynchronous data streams.
async function* asyncGenerator() {
let i = 0;
while (i < 3) {
await new Promise(resolve => setTimeout(resolve, 1000));
yield i++;
}
}
(async function() {
for await (const num of asyncGenerator()) {
console.log(num); // 0, 1, 2 (output one per second)
}
})();
Creating asynchronous iterators from Node.js streams:
import { createReadStream } from 'fs';
async function readFileLines(path) {
const stream = createReadStream(path, { encoding: 'utf8' });
for await (const chunk of stream) {
const lines = chunk.split('\n');
for (const line of lines) {
console.log(line);
}
}
}
Asynchronous Operations in Web Workers
Using Promises in Web Workers for cross-thread communication.
Main thread code:
const worker = new Worker('worker.js');
function sendToWorker(data) {
return new Promise((resolve) => {
worker.onmessage = (e) => resolve(e.data);
worker.postMessage(data);
});
}
async function processData() {
const result = await sendToWorker({ type: 'process', data: largeArray });
console.log('Processing result:', result);
}
Worker thread code (worker.js):
self.onmessage = async (e) => {
if (e.data.type === 'process') {
const result = await heavyProcessing(e.data.data);
self.postMessage(result);
}
};
async function heavyProcessing(data) {
// Simulate heavy processing
return new Promise(resolve => {
setTimeout(() => {
resolve(data.map(item => item * 2));
}, 1000);
});
}
Microtasks and the Event Loop
The execution order of Promise callbacks as microtasks in the event loop.
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
*/
Manually triggering the microtask queue:
function enqueueMicrotask(fn) {
if (typeof queueMicrotask === 'function') {
queueMicrotask(fn);
} else {
Promise.resolve().then(fn);
}
}
enqueueMicrotask(() => console.log('This is a microtask'));
Promise Cancellation
Native Promises don't support cancellation, but it can be implemented via wrappers.
Using AbortController to cancel fetch requests:
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal })
.then(response => response.json())
.catch(error => {
if (error.name === 'AbortError') {
console.log('Request canceled');
}
});
// Cancel the request
controller.abort();
Cancelable Promise wrapper:
function makeCancelable(promise) {
let isCanceled = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
value => isCanceled ? reject({ isCanceled: true }) : resolve(value),
error => isCanceled ? reject({ isCanceled: true }) : reject(error)
);
});
return {
promise: wrappedPromise,
cancel() {
isCanceled = true;
}
};
}
const { promise, cancel } = makeCancelable(fetch('/api/data'));
// Cancel the operation
cancel();
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:与Promise的互操作
下一篇:小程序的全球化发展前景