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ểm | Dùng khi |
|---|---|---|
let + setTimeout | Lên lịch tất cả ngay, chạy song song | Không cần chờ lần trước xong |
async/await | Chờ lần trước xong mới chạy lần sau | Cầ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
}
}