| description | Vibe coding guidelines and architectural constraints for JavaScript Asynchronous Logic within the frontend domain. | ||||||
|---|---|---|---|---|---|---|---|
| technology | JavaScript | ||||||
| domain | frontend | ||||||
| level | Senior/Architect | ||||||
| version | ES2024+ | ||||||
| tags |
|
||||||
| ai_role | Senior JavaScript Asynchronous Expert | ||||||
| last_updated | 2026-03-22 |
- Primary Goal: Implement correct and robust asynchronous logic in JavaScript applications.
- Target Tooling: Cursor, Windsurf, Antigravity.
- Tech Stack Version: ES2024+
Context: Managing asynchronous execution flow.
getData(url, (err, res) => {
getDetails(res.id, (err, details) => {
saveData(details, (err, ok) => {
// Callback Hell
});
});
});Deeply nested callbacks (the "Pyramid of Doom") make error handling extremely difficult and code unreadable.
fetchData(url)
.then(res => fetchDetails(res.id))
.then(details => saveData(details))
.catch(err => handleError(err));Use Promises to flatten the structure and centralize error handling with .catch().
Context: Modern syntax for asynchronous code.
function load() {
return api.get().then(res => {
return api.process(res).then(processed => {
return processed;
});
});
}Even with Promises, .then() nesting can occur. It still feels like "callback style" logic.
async function load() {
const res = await api.get();
const processed = await api.process(res);
return processed;
}Use async/await. It allows asynchronous code to be written and read like synchronous code, improving maintainability.
Context: Parallelizing independent asynchronous operations.
for (const id of ids) {
await fetchItem(id); // Pauses loop for each request
}Sequential await in a loop causes a "waterfall" effect, where each request waits for the previous one to finish, significantly increasing total execution time.
const promises = ids.map(id => fetchItem(id));
await Promise.all(promises);Use Promise.all to execute independent promises in parallel. This utilizes the full network/IO bandwidth.
Context: Handling failures in async functions.
async function getData() {
const data = await fetch(url); // If this fails, the process might crash
return data;
}Unhandled exceptions in async functions result in unhandled promise rejections, which can lead to silent failures or process termination in Node.js.
async function getData() {
try {
const data = await fetch(url);
return data;
} catch (error) {
logError(error);
}
}Wrap await calls in try/catch blocks or use a higher-order function to catch errors.
Context: Precision issues in IEEE 754 arithmetic.
if (0.1 + 0.2 === 0.3) { /* False! */ }const EPSILON = Number.EPSILON;
const areEqual = (a, b) => Math.abs(a - b) < EPSILON;
// Or for money:
const totalCents = (10 + 20); // 30 centsUse Number.EPSILON for comparisons or represent decimals as integers (e.g., cents instead of dollars) to avoid floating point drift.
Context: Managing complex component logic.
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);Multiple flags allow for "impossible states" (e.g., isLoading and isError both being true). This makes logic branches exponentially complex.
const [status, setStatus] = useState('IDLE'); // IDLE, LOADING, ERROR, SUCCESSUse a single state variable or a state machine. This ensures only one state is active at a time and simplifies transitions.
Context: Keeping the UI responsive.
function processLargeArray(arr) {
// Blocks the main thread for 2 seconds
arr.sort().forEach(item => complexCalc(item));
}JavaScript is single-threaded. Heavy synchronous computation blocks the Event Loop, causing the UI to freeze and preventing user interaction.
// Use Web Workers or break into chunks
function processInChunks(arr) {
if (arr.length === 0) return;
const chunk = arr.splice(0, 100);
process(chunk);
setTimeout(() => processInChunks(arr), 0);
}Offload heavy tasks to Web Workers or use requestIdleCallback/setTimeout to break long tasks into smaller chunks, allowing the browser to render between frames.
Context: Paradigm choice (OOP vs FP).
class Calculator {
add(a, b) { return a + b; }
}
const calc = new Calculator();Classes introduce unnecessary overhead (prototype chain, this binding issues) and make tree-shaking harder for bundlers.
export const add = (a, b) => a + b;Use simple functions and modules for logic. Use classes only when you need to manage complex stateful instances with shared behavior.
Context: Robust error handling and debugging.
throw new Error('User not found');Parsing error messages in catch blocks is brittle. If the string changes, the error handling logic breaks.
class UserNotFoundError extends Error {
constructor(userId) {
super(`User ${userId} not found`);
this.name = 'UserNotFoundError';
this.code = 404;
}
}Extend the Error class to create custom error types. Use instanceof check in catch blocks to handle specific errors differently.
Context: Reliability of asynchronous flows.
// No .catch() or try/catch
fetch('/api/data');Unhandled rejections create silent failures. In production environments, this can lead to memory leaks as the promise state stays pending or rejected without being cleared.
window.addEventListener('unhandledrejection', event => {
reportToSentry(event.reason);
});Always handle promise rejections. Implement a global unhandled rejection listener as a safety net for monitoring.