Asynchronous processing specification
Asynchronous processing is a crucial concept in JavaScript programming, and proper conventions can enhance code readability and maintainability. Below are common asynchronous processing conventions and best practices.
Promise Usage Conventions
Promises are fundamental tools for handling asynchronous operations. Follow these rules when using them:
- Chaining: Avoid nested Promises; use
.then()
chaining instead.
// Correct example
fetchData()
.then(processData)
.then(saveData)
.catch(handleError);
// Incorrect example
fetchData().then(data => {
processData(data).then(result => {
saveData(result).catch(err => {
handleError(err);
});
});
});
- Error Handling: Every Promise chain must end with
.catch()
.
getUserInfo()
.then(updateUI)
.catch(logError); // Errors must be caught
- Return Full Promises: Always return the full Promise object inside
.then()
.
// Correct
fetch('/api').then(response => {
return response.json();
});
// Incorrect
fetch('/api').then(response => {
response.json(); // Missing return
});
async/await Best Practices
ES2017 introduced async/await, making asynchronous code resemble synchronous code:
- Unified Error Handling: Wrap await calls with try-catch.
async function loadData() {
try {
const data = await fetchData();
const processed = await processData(data);
return processed;
} catch (error) {
console.error('Loading failed:', error);
throw error; // Re-throw for higher-level handling
}
}
- Avoid Redundant await: Use parallel execution for non-dependent operations.
// Inefficient
const user = await getUser();
const posts = await getPosts();
// Optimized
const [user, posts] = await Promise.all([
getUser(),
getPosts()
]);
- Explicit async Marking: Asynchronous functions must be explicitly declared with async.
// Correct
async function fetchResource() { /*...*/ }
// Incorrect
function fetchResource() { // Missing async
return await someAsyncOp();
}
Callback Function Conventions
For scenarios requiring callback functions (e.g., traditional Node.js APIs):
- Error-First Principle: The first parameter of a callback must be the error.
fs.readFile('config.json', (err, data) => {
if (err) return handleError(err);
processConfig(data);
});
- Avoid Deep Nesting: Refactor to Promises if nesting exceeds 3 levels.
// Code needing refactoring
getUser(userId, (err, user) => {
if (err) return console.error(err);
getPermissions(user, (err, permissions) => {
if (err) return console.error(err);
checkAccess(permissions, (err, access) => {
if (err) return console.error(err);
// Business logic...
});
});
});
Event Emitter Conventions
When handling EventEmitter-type asynchronous events:
- Clean Up Listeners: Remove event listeners when no longer needed.
const emitter = new EventEmitter();
function listener(data) {
console.log('Received:', data);
emitter.off('data', listener); // Clean up immediately after use
}
emitter.on('data', listener);
- Handle Error Events: Always listen for error events.
const stream = createReadStream('file.txt');
stream.on('error', (err) => {
console.error('Stream error:', err);
});
Microtasks and Macrotasks
Understanding execution order is critical for complex asynchronous logic:
console.log('Script start');
setTimeout(() => {
console.log('Macrotask');
}, 0);
Promise.resolve().then(() => {
console.log('Microtask');
});
console.log('Script end');
// Output order:
// Script start
// Script end
// Microtask
// Macrotask
Canceling Asynchronous Operations
Implementing cancelable asynchronous flows:
- AbortController Approach:
const controller = new AbortController();
fetch('/api', {
signal: controller.signal
}).catch(err => {
if (err.name === 'AbortError') {
console.log('Request canceled');
}
});
// Cancel the request
controller.abort();
- Custom Cancel Token:
function createCancellablePromise(executor) {
let rejectFn;
const promise = new Promise((resolve, reject) => {
rejectFn = reject;
executor(resolve, reject);
});
return {
promise,
cancel: (reason) => {
rejectFn(new Error(reason || 'Cancelled'));
}
};
}
Asynchronous State Management
Handling asynchronous operations in state management:
// Redux async action example
const fetchUser = (userId) => async (dispatch) => {
dispatch({ type: 'USER_FETCH_START' });
try {
const response = await api.getUser(userId);
dispatch({ type: 'USER_FETCH_SUCCESS', payload: response });
} catch (error) {
dispatch({ type: 'USER_FETCH_ERROR', error });
}
};
// Vuex example
actions: {
async loadPosts({ commit }) {
commit('SET_LOADING', true);
const posts = await fetchPosts();
commit('SET_POSTS', posts);
commit('SET_LOADING', false);
}
}
Performance Optimization Strategies
Practical tips for improving asynchronous operation performance:
- Request Deduplication: Avoid duplicate requests.
const pendingRequests = new Map();
async function cachedFetch(url) {
if (pendingRequests.has(url)) {
return pendingRequests.get(url);
}
const promise = fetch(url).then(res => res.json());
pendingRequests.set(url, promise);
try {
return await promise;
} finally {
pendingRequests.delete(url);
}
}
- Batch Processing: Combine multiple asynchronous operations.
async function batchUpdateItems(items) {
const BATCH_SIZE = 10;
const batches = [];
for (let i = 0; i < items.length; i += BATCH_SIZE) {
batches.push(items.slice(i, i + BATCH_SIZE));
}
const results = [];
for (const batch of batches) {
const batchResults = await Promise.all(
batch.map(item => updateItem(item))
);
results.push(...batchResults);
}
return results;
}
Testing Asynchronous Code
Writing reliable asynchronous test cases:
// Jest test example
describe('Asynchronous function testing', () => {
// Callback style
test('Callback execution', done => {
fetchData((err, data) => {
expect(err).toBeNull();
expect(data).toHaveProperty('id');
done();
});
});
// Promise style
test('Promise resolution', () => {
return fetchData().then(data => {
expect(data.status).toBe(200);
});
});
// async/await style
test('async/await test', async () => {
const data = await fetchData();
expect(data.items).toBeInstanceOf(Array);
});
// Timer mocking
jest.useFakeTimers();
test('Timer test', () => {
const callback = jest.fn();
delayedCallback(callback);
jest.runAllTimers();
expect(callback).toHaveBeenCalled();
});
});
Browser vs. Node Differences
Differences in asynchronous handling across environments:
- Global Object Differences:
// Browser environment
window.setTimeout(() => {
// Browser-specific APIs
}, 100);
// Node environment
global.setImmediate(() => {
// Node-specific APIs
});
- Module Loading Differences:
// Node CommonJS
const fs = require('fs');
fs.readFile('file.txt', 'utf8', (err, data) => {
// Callback handling
});
// Browser ES modules
import('./module.js').then(module => {
module.doSomething();
});
Error Tracking and Debugging
Enhancing debuggability of asynchronous errors:
- Add Context Information:
async function wrappedFetch(url) {
try {
return await fetch(url);
} catch (err) {
err.context = { url, timestamp: Date.now() };
throw err;
}
}
- Long-Running Task Monitoring:
function withTimeout(promise, timeout, taskName) {
return Promise.race([
promise,
new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`${taskName} timed out (${timeout}ms)`));
}, timeout);
})
]);
}
Concurrency Control
Managing the number of parallel asynchronous tasks:
class TaskQueue {
constructor(concurrency) {
this.concurrency = concurrency;
this.running = 0;
this.queue = [];
}
push(task) {
this.queue.push(task);
this.next();
}
next() {
while (this.running < this.concurrency && this.queue.length) {
const task = this.queue.shift();
task().finally(() => {
this.running--;
this.next();
});
this.running++;
}
}
}
// Usage example
const queue = new TaskQueue(3);
urls.forEach(url => {
queue.push(() => fetchUrl(url));
});
Evolution of Asynchronous Patterns
Historical perspective on asynchronous processing:
- Callback Hell Era:
// Typical 2013 code
readFile('a.txt', (err, a) => {
if (err) return handle(err);
readFile('b.txt', (err, b) => {
if (err) return handle(err);
writeFile('c.txt', a + b, (err) => {
if (err) return handle(err);
console.log('Done!');
});
});
});
- Promise Transition Period:
// Typical 2015 improvement
readFile('a.txt')
.then(a => readFile('b.txt').then(b => [a, b]))
.then(([a, b]) => writeFile('c.txt', a + b))
.then(() => console.log('Done'))
.catch(handle);
- Modern async/await:
// 2020+ modern style
async function concatFiles() {
try {
const a = await readFile('a.txt');
const b = await readFile('b.txt');
await writeFile('c.txt', a + b);
console.log('Done');
} catch (err) {
handle(err);
}
}
Utility Functions
Collection of commonly used asynchronous helpers:
- Retry Mechanism:
async function retry(fn, retries = 3, delay = 1000) {
try {
return await fn();
} catch (err) {
if (retries <= 0) throw err;
await new Promise(res => setTimeout(res, delay));
return retry(fn, retries - 1, delay * 2);
}
}
- Progress Notification:
async function withProgress(promise, onProgress) {
let progress = 0;
const interval = setInterval(() => {
progress = Math.min(progress + 10, 90);
onProgress(progress);
}, 100);
try {
const result = await promise;
clearInterval(interval);
onProgress(100);
return result;
} catch (err) {
clearInterval(interval);
throw err;
}
}
Browser API Integration
Asynchronous patterns with browser APIs:
- Web Worker Communication:
// Main thread
const worker = new Worker('task.js');
worker.postMessage({ cmd: 'start', data: 42 });
worker.onmessage = (e) => {
console.log('Result:', e.data);
};
// worker.js
self.onmessage = async (e) => {
if (e.data.cmd === 'start') {
const result = await heavyComputation(e.data.data);
self.postMessage(result);
}
};
- IndexedDB Transactions:
async function saveRecord(dbName, record) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(dbName);
request.onsuccess = (e) => {
const db = e.target.result;
const tx = db.transaction('store', 'readwrite');
const store = tx.objectStore('store');
const putRequest = store.put(record);
putRequest.onsuccess = () => resolve();
putRequest.onerror = (e) => reject(e.target.error);
};
request.onerror = (e) => reject(e.target.error);
});
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn