Skip to content

Latest commit

 

History

History
230 lines (215 loc) · 7.41 KB

File metadata and controls

230 lines (215 loc) · 7.41 KB
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
javascript
async
promises
best-practices
clean-code
scalable-code
ai_role Senior JavaScript Asynchronous Expert
last_updated 2026-03-22

⏳ JavaScript Asynchronous & Logic Best Practices

📖 Context & Scope

  • Primary Goal: Implement correct and robust asynchronous logic in JavaScript applications.
  • Target Tooling: Cursor, Windsurf, Antigravity.
  • Tech Stack Version: ES2024+

III. Asynchronous & Logic

21. Callback Hell vs Promises

Context: Managing asynchronous execution flow.

❌ Bad Practice

getData(url, (err, res) => {
    getDetails(res.id, (err, details) => {
        saveData(details, (err, ok) => {
            // Callback Hell
        });
    });
});

⚠️ Problem

Deeply nested callbacks (the "Pyramid of Doom") make error handling extremely difficult and code unreadable.

✅ Best Practice

fetchData(url)
    .then(res => fetchDetails(res.id))
    .then(details => saveData(details))
    .catch(err => handleError(err));

🚀 Solution

Use Promises to flatten the structure and centralize error handling with .catch().

22. Promise.then() nesting vs Async/Await

Context: Modern syntax for asynchronous code.

❌ Bad Practice

function load() {
    return api.get().then(res => {
        return api.process(res).then(processed => {
            return processed;
        });
    });
}

⚠️ Problem

Even with Promises, .then() nesting can occur. It still feels like "callback style" logic.

✅ Best Practice

async function load() {
    const res = await api.get();
    const processed = await api.process(res);
    return processed;
}

🚀 Solution

Use async/await. It allows asynchronous code to be written and read like synchronous code, improving maintainability.

23. Sequential await in loops vs Promise.all

Context: Parallelizing independent asynchronous operations.

❌ Bad Practice

for (const id of ids) {
    await fetchItem(id); // Pauses loop for each request
}

⚠️ Problem

Sequential await in a loop causes a "waterfall" effect, where each request waits for the previous one to finish, significantly increasing total execution time.

✅ Best Practice

const promises = ids.map(id => fetchItem(id));
await Promise.all(promises);

🚀 Solution

Use Promise.all to execute independent promises in parallel. This utilizes the full network/IO bandwidth.

24. Missing try/catch in async

Context: Handling failures in async functions.

❌ Bad Practice

async function getData() {
    const data = await fetch(url); // If this fails, the process might crash
    return data;
}

⚠️ Problem

Unhandled exceptions in async functions result in unhandled promise rejections, which can lead to silent failures or process termination in Node.js.

✅ Best Practice

async function getData() {
    try {
        const data = await fetch(url);
        return data;
    } catch (error) {
        logError(error);
    }
}

🚀 Solution

Wrap await calls in try/catch blocks or use a higher-order function to catch errors.

25. Floating point math errors (0.1 + 0.2)

Context: Precision issues in IEEE 754 arithmetic.

❌ Bad Practice

if (0.1 + 0.2 === 0.3) { /* False! */ }

⚠️ Problem

$\text{0.1} + \text{0.2} = \text{0.30000000000000004}$ due to binary representation limits. This leads to critical bugs in financial or scientific applications.

✅ Best Practice

const EPSILON = Number.EPSILON;
const areEqual = (a, b) => Math.abs(a - b) < EPSILON;

// Or for money:
const totalCents = (10 + 20); // 30 cents

🚀 Solution

Use Number.EPSILON for comparisons or represent decimals as integers (e.g., cents instead of dollars) to avoid floating point drift.

26. Multiple Boolean flags vs State Machine

Context: Managing complex component logic.

❌ Bad Practice

const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);

⚠️ Problem

Multiple flags allow for "impossible states" (e.g., isLoading and isError both being true). This makes logic branches exponentially complex.

✅ Best Practice

const [status, setStatus] = useState('IDLE'); // IDLE, LOADING, ERROR, SUCCESS

🚀 Solution

Use a single state variable or a state machine. This ensures only one state is active at a time and simplifies transitions.

27. Sync logic in Event Loop

Context: Keeping the UI responsive.

❌ Bad Practice

function processLargeArray(arr) {
    // Blocks the main thread for 2 seconds
    arr.sort().forEach(item => complexCalc(item));
}

⚠️ Problem

JavaScript is single-threaded. Heavy synchronous computation blocks the Event Loop, causing the UI to freeze and preventing user interaction.

✅ Best Practice

// 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);
}

🚀 Solution

Offload heavy tasks to Web Workers or use requestIdleCallback/setTimeout to break long tasks into smaller chunks, allowing the browser to render between frames.

28. Overusing classes where functions suffice

Context: Paradigm choice (OOP vs FP).

❌ Bad Practice

class Calculator {
    add(a, b) { return a + b; }
}
const calc = new Calculator();

⚠️ Problem

Classes introduce unnecessary overhead (prototype chain, this binding issues) and make tree-shaking harder for bundlers.

✅ Best Practice

export const add = (a, b) => a + b;

🚀 Solution

Use simple functions and modules for logic. Use classes only when you need to manage complex stateful instances with shared behavior.

29. Hard-coded Error messages vs Error Classes

Context: Robust error handling and debugging.

❌ Bad Practice

throw new Error('User not found');

⚠️ Problem

Parsing error messages in catch blocks is brittle. If the string changes, the error handling logic breaks.

✅ Best Practice

class UserNotFoundError extends Error {
    constructor(userId) {
        super(`User ${userId} not found`);
        this.name = 'UserNotFoundError';
        this.code = 404;
    }
}

🚀 Solution

Extend the Error class to create custom error types. Use instanceof check in catch blocks to handle specific errors differently.

30. Unhandled Rejections

Context: Reliability of asynchronous flows.

❌ Bad Practice

// No .catch() or try/catch
fetch('/api/data');

⚠️ Problem

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.

✅ Best Practice

window.addEventListener('unhandledrejection', event => {
    reportToSentry(event.reason);
});

🚀 Solution

Always handle promise rejections. Implement a global unhandled rejection listener as a safety net for monitoring.