Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> JavaScript

Thêm độ trễ (Delay) trong vòng lặp JavaScript

Vấn đề

Vòng lặp for chạy đồng bộ, không chờ setTimeout. Code sau chạy ngay lập tức.

// ❌ SAI - In ra 10 lần "i = 10" sau 2 giây
for (let i = 0; i < 10; i++) {
    setTimeout(() => console.log(i), 2000);
}

Giải pháp

1. IIFE / Closure (ES5)

for (var i = 0; i < 10; i++) {
    (function(i) {
        setTimeout(() => console.log(i), 2000 * i);
    })(i);
}

2. let trong for loop (ES6 - Khuyến nghị)

// ✅ ĐÚNG - let tạo block scope mới mỗi iteration
for (let i = 0; i < 10; i++) {
    setTimeout(() => console.log(i), 2000 * i);
}
// In: 0, 1, 2... 9 mỗi 2 giây

3. Async/Await + Promise (Sequential)

async function delayedLoop() {
    for (let i = 0; i < 10; i++) {
        await new Promise(r => setTimeout(r, 2000));
        console.log(i);
    }
}

4. Promise.all (Parallel - không phải delay tuần tự)

// Chạy song song - không phải delay giữa các lần
await Promise.all(
    Array.from({length: 10}, (_, i) => 
        new Promise(r => setTimeout(() => { console.log(i); r(); }, 2000 * i))
    )
);

Ví dụ hoàn chỉnh (UI)

<!DOCTYPE html>
<html lang="vi">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Delay in Loop</title>
    <style>
        body { font-family: 'Segoe UI', sans-serif; padding: 20px; }
        .result { font-size: 16px; font-weight: 500; color: #6a1b9a; margin: 20px 0; padding: 15px; background: #f3e5f5; border-radius: 4px; min-height: 200px; white-space: pre-wrap; font-family: monospace; }
        button { padding: 10px 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; margin-right: 10px; }
        button:disabled { background: #90caf9; cursor: not-allowed; }
    </style>
</head>
<body>
    <h1>Thêm Delay trong Vòng lặp</h1>
    <div class="result" id="result">Nhấn BẮT ĐẦU...</div>
    <button id="btnStart">BẮT ĐẦU (let + setTimeout)</button>
    <button id="btnAsync">ASYNC/AWAIT (Sequential)</button>
    <button id="btnClear">XÓA</button>

    <script>
        const resEle = document.getElementById('result');
        const btnStart = document.getElementById('btnStart');
        const btnAsync = document.getElementById('btnAsync');
        const btnClear = document.getElementById('btnClear');

        function log(msg) {
            resEle.textContent += msg + '\n';
            resEle.scrollTop = resEle.scrollHeight;
        }

        btnStart.addEventListener('click', async () => {
            btnStart.disabled = true;
            btnAsync.disabled = true;
            resEle.textContent = '';
            
            // Cách 1: let + setTimeout (parallel scheduling)
            log('🚀 Bắt đầu: let + setTimeout');
            for (let i = 0; i < 5; i++) {
                setTimeout(() => log(`  i = ${i}`), 1000 * i);
            }
            
            // Đợi xong để enable buttons
            await new Promise(r => setTimeout(r, 6000));
            btnStart.disabled = false;
            btnAsync.disabled = false;
        });

        btnAsync.addEventListener('click', async () => {
            btnStart.disabled = true;
            btnAsync.disabled = true;
            resEle.textContent = '';
            
            // Cách 2: async/await (sequential - chờ từng cái)
            log('🚀 Bắt đầu: async/await sequential');
            for (let i = 0; i < 5; i++) {
                await new Promise(r => setTimeout(r, 1000));
                log(`  i = ${i}`);
            }
            
            btnStart.disabled = false;
            btnAsync.disabled = false;
        });

        btnClear.addEventListener('click', () => {
            resEle.textContent = 'Nhấn BẮT ĐẦU...';
        });
    </script>
</body>
</html>

So sánh 2 cách

CáchĐặc điểmDùng khi
let + setTimeoutLên lịch tất cả ngay, chạy song songKhông cần chờ lần trước xong
async/awaitChờ lần trước xong mới chạy lần sauCần thứ tự, tránh rate limit API

Utility Function: delay

const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

// Usage
async function processItems(items) {
    for (const item of items) {
        await processItem(item);
        await delay(1000); // Delay 1s giữa các item
    }
}