no-await-in-loop
ESLint rule documentation for no-await-in-loop
📡 Live from GitHub — This documentation is fetched directly from no-await-in-loop.md and cached for 6 hours.
Keywords: async, await, loop, Promise.all, concurrency, performance, ESLint rule, sequential, parallel, LLM-optimized
Disallow await inside loops without considering concurrency implications
Disallow await inside loops without considering concurrency implications. This rule is part of eslint-plugin-reliability and provides LLM-optimized error messages with concurrency pattern suggestions.
Quick Summary
| Aspect | Details |
|---|---|
| Severity | Warning (performance) |
| Auto-Fix | ❌ No (suggests refactoring patterns) |
| Category | Quality |
| ESLint MCP | ✅ Optimized for ESLint MCP integration |
| Best For | Performance-critical applications, API batch operations |
Rule Details
Why This Matters
| Issue | Impact | Solution |
|---|---|---|
| ⚡ Performance | N*latency instead of 1x | Use Promise.all() |
| 🔄 Sequential Bottleneck | Blocks event loop | Concurrent execution |
| 📊 Scalability | Slow with large datasets | Batch processing |
| 🎯 Resource Usage | Inefficient API calls | Parallelization |
Configuration
| Option | Type | Default | Description |
|---|---|---|---|
allowForOf | boolean | false | Allow await in for-of loops |
allowWhile | boolean | false | Allow await in while loops |
checkConcurrency | boolean | true | Check for potential concurrent execution |
Examples
❌ Incorrect
// Sequential execution - N * latency
async function fetchAllUsers(ids: string[]) {
const users = [];
for (const id of ids) {
const user = await fetchUser(id); // ❌ Await in loop
users.push(user);
}
return users;
}
// Also problematic
async function processItems(items: Item[]) {
for (let i = 0; i < items.length; i++) {
await processItem(items[i]); // ❌ Await in loop
}
}✅ Correct
// Concurrent execution - 1x latency (max)
async function fetchAllUsers(ids: string[]) {
const users = await Promise.all(
ids.map(id => fetchUser(id))
);
return users;
}
// With error handling
async function processItems(items: Item[]) {
const results = await Promise.allSettled(
items.map(item => processItem(item))
);
return results;
}
// Controlled concurrency with p-map
import pMap from 'p-map';
async function fetchWithLimit(urls: string[]) {
return pMap(urls, fetchUrl, { concurrency: 5 });
}Configuration Examples
Basic Usage
{
rules: {
'architecture/no-await-in-loop': 'warn'
}
}Allow for-of Loops
{
rules: {
'architecture/no-await-in-loop': ['warn', {
allowForOf: true,
allowWhile: false
}]
}
}Strict Mode
{
rules: {
'architecture/no-await-in-loop': ['error', {
allowForOf: false,
allowWhile: false,
checkConcurrency: true
}]
}
}Refactoring Patterns
Sequential to Parallel
// ❌ Before: 10 items = 10 * 100ms = 1000ms
for (const item of items) {
await process(item);
}
// ✅ After: 10 items = ~100ms (parallel)
await Promise.all(items.map(process));With Rate Limiting
// Using p-map for controlled concurrency
import pMap from 'p-map';
async function fetchAll(urls: string[]) {
return pMap(urls, fetch, { concurrency: 3 });
}When Sequential is Required
// ✅ Extract to separate function if sequential is needed
async function processSequentially(items: Item[]) {
const results = [];
for (const item of items) {
// Dependencies between iterations require sequential
const result = await processWithPrevious(item, results);
results.push(result);
}
return results;
}When Not To Use
| Scenario | Recommendation |
|---|---|
| 📊 Order-dependent ops | Use allowForOf: true or disable rule |
| 🔒 Rate-limited APIs | Use controlled concurrency libraries |
| 💾 Memory constraints | Sequential may be necessary |
| 🔄 Transaction chains | Sequential execution required |
Comparison with Alternatives
| Feature | no-await-in-loop | eslint built-in | unicorn |
|---|---|---|---|
| Concurrency Check | ✅ Yes | ❌ No | ⚠️ Limited |
| Loop Type Config | ✅ Per-type | ❌ No | ❌ No |
| LLM-Optimized | ✅ Yes | ❌ No | ❌ No |
| ESLint MCP | ✅ Optimized | ❌ No | ❌ No |
| Pattern Suggestions | ✅ Yes | ❌ No | ⚠️ Limited |
Related Rules
detect-n-plus-one-queries- Detects N+1 query patternsno-blocking-operations- Detects blocking operations
Further Reading
- Promise.all() - MDN - Concurrent promise execution
- Promise.allSettled() - MDN - Handle mixed results
- p-map - Controlled concurrency library
- ESLint MCP Setup - Enable AI assistant integration
Known False Negatives
The following patterns are not detected due to static analysis limitations:
Dynamic Variable References
Why: Static analysis cannot trace values stored in variables or passed through function parameters.
// ❌ NOT DETECTED - Value from variable
const value = externalSource();
processValue(value); // Variable origin not trackedMitigation: Implement runtime validation and review code manually. Consider using TypeScript branded types for validated inputs.
Wrapped or Aliased Functions
Why: Custom wrapper functions or aliased methods are not recognized by the rule.
// ❌ NOT DETECTED - Custom wrapper
function myWrapper(data) {
return internalApi(data); // Wrapper not analyzed
}
myWrapper(unsafeInput);Mitigation: Apply this rule's principles to wrapper function implementations. Avoid aliasing security-sensitive functions.
Imported Values
Why: When values come from imports, the rule cannot analyze their origin or construction.
// ❌ NOT DETECTED - Value from import
import { getValue } from './helpers';
processValue(getValue()); // Cross-file not trackedMitigation: Ensure imported values follow the same constraints. Use TypeScript for type safety.