跳转到内容

Node.js 异步编程与并发控制实战 2026 | Event Loop 完全指南

Node.js 异步编程与并发控制

Node.js 的异步特性是其高性能的核心,但也是最容易出错的地方。理解 Event Loop 原理和掌握并发控制技巧,是编写高质量 Node.js 代码的关键。本文将从 Event Loop 深入到各种异步模式,再到实战级并发控制方案。


一、Event Loop 原理深度解析

1.1 Event Loop 阶段

┌─────────────────────────────────────────────────────────────┐
│                    Event Loop 执行顺序                      │
├─────────────────────────────────────────────────────────────┤
│  1. timers          → setTimeout/setInterval 回调          │
│  2. pending callbacks → I/O 回调(除 close、timer、setImmediate)│
│  3. idle, prepare   → 内部使用                             │
│  4. poll            → 轮询新的 I/O 事件                    │
│     ├─ 执行 poll 队列中的回调                              │
│     └─ 如果 timers 队列有任务,跳到 timers                  │
│  5. check           → setImmediate 回调                   │
│  6. close callbacks → close 事件回调                       │
│     └─ 回到 timers(循环)                                │
└─────────────────────────────────────────────────────────────┘

1.2 微任务与宏任务

typescript
// 微任务队列(Microtask Queue)
Promise.resolve().then(() => console.log('micro 1'))
queueMicrotask(() => console.log('micro 2'))

// 宏任务队列(Macrotask Queue)
setTimeout(() => console.log('macro 1'), 0)
setImmediate(() => console.log('macro 2'))

// 执行顺序:所有微任务 → 下一个宏任务 → 所有微任务 → ...

微任务优先级高于宏任务,在每个阶段结束时都会清空微任务队列。

1.3 setTimeout vs setImmediate

typescript
// 在主模块中执行,顺序不确定
setTimeout(() => console.log('timeout'), 0)
setImmediate(() => console.log('immediate'))

// 在 I/O 回调中执行,setImmediate 总是先执行
fs.readFile('/etc/hosts', () => {
  setTimeout(() => console.log('timeout'), 0)
  setImmediate(() => console.log('immediate'))  // 先执行
})

1.4 process.nextTick 特殊地位

process.nextTick 不属于任何阶段,它在每个阶段切换之前执行:

typescript
Promise.resolve().then(() => console.log('promise'))
process.nextTick(() => console.log('nextTick'))  // 最先执行

// 输出:nextTick → promise

二、异步编程模式演进

2.1 回调地狱(Callback Hell)

typescript
fs.readFile('config.json', (err, data) => {
  if (err) throw err
  const config = JSON.parse(data)
  
  db.connect(config.db, (err, conn) => {
    if (err) throw err
    
    conn.query('SELECT * FROM users', (err, users) => {
      if (err) throw err
      
      users.forEach(user => {
        sendEmail(user.email, (err) => {
          if (err) console.error(err)
        })
      })
    })
  })
})

2.2 Promise 模式

typescript
const readFile = promisify(fs.readFile)

readFile('config.json', 'utf8')
  .then(data => JSON.parse(data))
  .then(config => db.connect(config.db))
  .then(conn => conn.query('SELECT * FROM users'))
  .then(users => Promise.all(
    users.map(user => sendEmail(user.email))
  ))
  .catch(err => console.error(err))

2.3 async/await 模式

typescript
async function processUsers() {
  try {
    const data = await readFile('config.json', 'utf8')
    const config = JSON.parse(data)
    const conn = await db.connect(config.db)
    const users = await conn.query('SELECT * FROM users')
    
    await Promise.all(users.map(user => sendEmail(user.email)))
    
    return users.length
  } catch (err) {
    console.error('处理失败:', err)
    throw err
  }
}

processUsers().then(count => console.log(`处理了 ${count} 个用户`))

2.4 Generator + co 模式

typescript
const co = require('co')

co(function* () {
  const data = yield readFile('config.json', 'utf8')
  const config = JSON.parse(data)
  const conn = yield db.connect(config.db)
  const users = yield conn.query('SELECT * FROM users')
  
  yield users.map(user => sendEmail(user.email))
  return users.length
}).then(count => console.log(`处理了 ${count} 个用户`))

三、Promise 进阶技巧

3.1 Promise.all / allSettled / race / any

typescript
// Promise.all:全部成功,任一失败则失败
const results = await Promise.all([
  fetchData('users'),
  fetchData('posts'),
  fetchData('comments')
])

// Promise.allSettled:全部完成,返回每个的状态
const results = await Promise.allSettled([
  fetchData('users'),
  fetchData('posts'),
  fetchData('comments')
])
results.forEach(result => {
  if (result.status === 'fulfilled') {
    console.log('成功:', result.value)
  } else {
    console.log('失败:', result.reason)
  }
})

// Promise.race:第一个完成的
const result = await Promise.race([
  fetchDataWithTimeout(url, 5000),
  timeoutPromise(5000)
])

// Promise.any:第一个成功的(ES2021)
const result = await Promise.any([
  fetchFromServer1(),
  fetchFromServer2(),
  fetchFromServer3()
])

3.2 带超时的 Promise

typescript
function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
  const timeout = new Promise<never>((_, reject) => {
    setTimeout(() => reject(new Error(`超时 ${ms}ms`)), ms)
  })
  return Promise.race([promise, timeout])
}

// 使用
const data = await withTimeout(fetchData(url), 5000)

3.3 Promise 链式错误处理

typescript
fetchData()
  .then(result => processResult(result))
  .then(output => saveOutput(output))
  .catch(err => {
    // 捕获上面所有阶段的错误
    console.error('流程失败:', err)
    return fallbackValue
  })
  .finally(() => {
    // 无论成功失败都执行
    cleanup()
  })

四、并发控制实战

4.1 串行执行

typescript
async function serialExecute<T>(tasks: (() => Promise<T>)[]): Promise<T[]> {
  const results: T[] = []
  for (const task of tasks) {
    const result = await task()
    results.push(result)
  }
  return results
}

// 使用
const urls = ['url1', 'url2', 'url3']
const data = await serialExecute(urls.map(url => () => fetch(url)))

4.2 并行执行(无限制)

typescript
async function parallelExecute<T>(tasks: (() => Promise<T>)[]): Promise<T[]> {
  return Promise.all(tasks.map(task => task()))
}

// 使用
const data = await parallelExecute(urls.map(url => () => fetch(url)))

4.3 并发数限制(核心)

typescript
async function limitExecute<T>(
  tasks: (() => Promise<T>)[],
  limit: number
): Promise<T[]> {
  const results: T[] = []
  const executing = new Set<Promise<T>>()

  for (const task of tasks) {
    const promise = task().then(result => {
      executing.delete(promise)
      return result
    })

    executing.add(promise)
    results.push(promise)

    if (executing.size >= limit) {
      await Promise.race(executing)
    }
  }

  return Promise.all(results)
}

// 使用:最多同时 5 个并发
const data = await limitExecute(urls.map(url => () => fetch(url)), 5)

4.4 异步任务队列

typescript
class AsyncQueue<T> {
  private tasks: (() => Promise<T>)[] = []
  private executing = 0
  private maxConcurrency: number
  private resolve?: (() => void)

  constructor(maxConcurrency: number = 5) {
    this.maxConcurrency = maxConcurrency
  }

  add(task: () => Promise<T>): Promise<T> {
    return new Promise((resolve, reject) => {
      this.tasks.push(async () => {
        try {
          const result = await task()
          resolve(result)
        } catch (err) {
          reject(err)
        }
      })
      this.run()
    })
  }

  private async run() {
    while (this.executing < this.maxConcurrency && this.tasks.length > 0) {
      const task = this.tasks.shift()!
      this.executing++

      try {
        await task()
      } finally {
        this.executing--
        this.run()
      }
    }

    if (this.executing === 0 && this.tasks.length === 0 && this.resolve) {
      this.resolve()
    }
  }

  done(): Promise<void> {
    if (this.executing === 0 && this.tasks.length === 0) {
      return Promise.resolve()
    }
    return new Promise(resolve => {
      this.resolve = resolve
    })
  }
}

// 使用
const queue = new AsyncQueue(3)
urls.forEach(url => queue.add(() => fetch(url)))
await queue.done()

五、限流策略

5.1 时间窗口限流

typescript
class RateLimiter {
  private requests: number[] = []
  private maxRequests: number
  private windowMs: number

  constructor(maxRequests: number, windowMs: number) {
    this.maxRequests = maxRequests
    this.windowMs = windowMs
  }

  async acquire(): Promise<void> {
    const now = Date.now()
    
    // 移除窗口外的请求记录
    this.requests = this.requests.filter(t => now - t < this.windowMs)
    
    if (this.requests.length >= this.maxRequests) {
      // 计算需要等待的时间
      const waitTime = this.windowMs - (now - this.requests[0])
      await new Promise(resolve => setTimeout(resolve, waitTime))
      await this.acquire() // 递归检查
    } else {
      this.requests.push(now)
    }
  }
}

// 使用:每秒最多 10 次请求
const limiter = new RateLimiter(10, 1000)
async function fetchWithLimit(url: string) {
  await limiter.acquire()
  return fetch(url)
}

5.2 令牌桶算法

typescript
class TokenBucket {
  private tokens: number = 0
  private maxTokens: number
  private refillRate: number // tokens per second
  private lastRefill: number = Date.now()

  constructor(maxTokens: number, refillRate: number) {
    this.maxTokens = maxTokens
    this.refillRate = refillRate
    this.tokens = maxTokens
  }

  private refill() {
    const now = Date.now()
    const elapsed = (now - this.lastRefill) / 1000
    const newTokens = elapsed * this.refillRate
    
    this.tokens = Math.min(this.maxTokens, this.tokens + newTokens)
    this.lastRefill = now
  }

  async acquire(count: number = 1): Promise<void> {
    this.refill()
    
    if (this.tokens >= count) {
      this.tokens -= count
      return
    }

    // 计算需要等待的时间
    const waitTime = ((count - this.tokens) / this.refillRate) * 1000
    await new Promise(resolve => setTimeout(resolve, waitTime))
    await this.acquire(count)
  }
}

// 使用:最大 100 个令牌,每秒补充 10 个
const bucket = new TokenBucket(100, 10)
await bucket.acquire() // 获取 1 个令牌

5.3 漏桶算法

typescript
class LeakyBucket {
  private queue: (() => void)[] = []
  private maxSize: number
  private leakRate: number // items per second
  private leaking: boolean = false

  constructor(maxSize: number, leakRate: number) {
    this.maxSize = maxSize
    this.leakRate = leakRate
  }

  async add<T>(task: () => Promise<T>): Promise<T> {
    return new Promise((resolve, reject) => {
      if (this.queue.length >= this.maxSize) {
        reject(new Error('队列已满'))
        return
      }

      this.queue.push(async () => {
        try {
          const result = await task()
          resolve(result)
        } catch (err) {
          reject(err)
        }
      })

      this.startLeaking()
    })
  }

  private startLeaking() {
    if (this.leaking) return
    this.leaking = true

    const leak = () => {
      if (this.queue.length > 0) {
        const task = this.queue.shift()!
        task()
      }

      if (this.queue.length > 0) {
        setTimeout(leak, 1000 / this.leakRate)
      } else {
        this.leaking = false
      }
    }

    leak()
  }
}

// 使用:队列最多 50 个任务,每秒处理 5 个
const bucket = new LeakyBucket(50, 5)
const result = await bucket.add(() => fetch(url))

六、重试机制

6.1 简单重试

typescript
async function retry<T>(
  task: () => Promise<T>,
  maxAttempts: number,
  delayMs: number = 1000
): Promise<T> {
  let lastError: Error | undefined
  
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await task()
    } catch (err) {
      lastError = err as Error
      console.warn(`尝试 ${attempt}/${maxAttempts} 失败:`, err)
      
      if (attempt < maxAttempts) {
        await new Promise(resolve => setTimeout(resolve, delayMs * attempt))
      }
    }
  }
  
  throw lastError || new Error('重试失败')
}

// 使用:最多重试 3 次
const data = await retry(() => fetchData(url), 3)

6.2 指数退避重试

typescript
async function retryWithBackoff<T>(
  task: () => Promise<T>,
  maxAttempts: number,
  baseDelay: number = 1000
): Promise<T> {
  let lastError: Error | undefined
  
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await task()
    } catch (err) {
      lastError = err as Error
      console.warn(`尝试 ${attempt}/${maxAttempts} 失败:`, err)
      
      if (attempt < maxAttempts) {
        const delay = baseDelay * Math.pow(2, attempt - 1) + Math.random() * 500
        await new Promise(resolve => setTimeout(resolve, delay))
      }
    }
  }
  
  throw lastError || new Error('重试失败')
}

// 使用:指数退避 + 随机抖动
const data = await retryWithBackoff(() => fetchData(url), 5)

6.3 可中断重试

typescript
async function retryWithAbort<T>(
  task: () => Promise<T>,
  maxAttempts: number,
  signal?: AbortSignal
): Promise<T> {
  let lastError: Error | undefined
  
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    if (signal?.aborted) {
      throw new Error('操作已取消')
    }
    
    try {
      return await task()
    } catch (err) {
      lastError = err as Error
      
      if (attempt < maxAttempts && !signal?.aborted) {
        await new Promise(resolve => setTimeout(resolve, 1000 * attempt))
      }
    }
  }
  
  throw lastError || new Error('重试失败')
}

// 使用:支持 AbortController 取消
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 30000)

try {
  const data = await retryWithAbort(() => fetchData(url), 5, controller.signal)
} finally {
  clearTimeout(timeout)
}

七、Worker Threads 并发

7.1 基本用法

typescript
// worker.ts
import { parentPort, workerData } from 'worker_threads'

function fibonacci(n: number): number {
  if (n <= 1) return n
  return fibonacci(n - 1) + fibonacci(n - 2)
}

const result = fibonacci(workerData)
parentPort?.postMessage(result)
typescript
// main.ts
import { Worker } from 'worker_threads'

function runWorker(data: number): Promise<number> {
  return new Promise((resolve, reject) => {
    const worker = new Worker('./worker.ts', { workerData: data })
    
    worker.on('message', resolve)
    worker.on('error', reject)
    worker.on('exit', (code) => {
      if (code !== 0) {
        reject(new Error(`Worker exited with code ${code}`))
      }
    })
  })
}

// 使用
const result = await runWorker(40)

7.2 Worker 池

typescript
import { Worker, isMainThread, parentPort, workerData } from 'worker_threads'

if (!isMainThread) {
  // Worker 线程
  const result = heavyComputation(workerData)
  parentPort?.postMessage(result)
  process.exit(0)
}

class WorkerPool {
  private workers: Worker[] = []
  private tasks: { resolve: (value: any) => void; data: any }[] = []
  private idleWorkers: Worker[] = []

  constructor(size: number) {
    for (let i = 0; i < size; i++) {
      this.createWorker()
    }
  }

  private createWorker() {
    const worker = new Worker(__filename)
    
    worker.on('message', (result) => {
      const task = this.tasks.shift()
      task?.resolve(result)
      this.idleWorkers.push(worker)
    })

    worker.on('error', () => {
      this.workers = this.workers.filter(w => w !== worker)
      this.createWorker()
    })

    this.workers.push(worker)
    this.idleWorkers.push(worker)
  }

  async execute(data: any): Promise<any> {
    if (this.idleWorkers.length > 0) {
      const worker = this.idleWorkers.pop()!
      worker.postMessage(data)
    }

    return new Promise(resolve => {
      this.tasks.push({ resolve, data })
    })
  }

  destroy() {
    this.workers.forEach(worker => worker.terminate())
  }
}

// 使用
const pool = new WorkerPool(4)
const results = await Promise.all([
  pool.execute(data1),
  pool.execute(data2),
  pool.execute(data3),
  pool.execute(data4)
])
pool.destroy()

八、错误处理最佳实践

8.1 全局错误捕获

typescript
// 未捕获的 Promise 拒绝
process.on('unhandledRejection', (reason, promise) => {
  console.error('未处理的 Promise 拒绝:', reason)
  // 发送到监控系统
})

// 未捕获的异常
process.on('uncaughtException', (err) => {
  console.error('未捕获的异常:', err)
  // 优雅关闭服务
  server.close(() => process.exit(1))
})

// unhandledRejection 监听后不会自动退出,但 uncaughtException 会

8.2 结构化错误处理

typescript
class AppError extends Error {
  constructor(
    public message: string,
    public code: string,
    public status: number = 500,
    public details?: any
  ) {
    super(message)
    this.name = 'AppError'
  }
}

// 使用
async function fetchUser(id: string) {
  const user = await db.query('SELECT * FROM users WHERE id = ?', [id])
  if (!user) {
    throw new AppError('用户不存在', 'USER_NOT_FOUND', 404, { id })
  }
  return user
}

// 中间件统一处理
app.use(async (ctx, next) => {
  try {
    await next()
  } catch (err) {
    if (err instanceof AppError) {
      ctx.status = err.status
      ctx.body = {
        code: err.code,
        message: err.message,
        details: err.details
      }
    } else {
      ctx.status = 500
      ctx.body = { code: 'INTERNAL_ERROR', message: '服务器内部错误' }
    }
  }
})

九、性能监控与调试

9.1 异步耗时监控

typescript
function traceAsync<T>(name: string, fn: () => Promise<T>): Promise<T> {
  const start = Date.now()
  return fn().then(result => {
    console.log(`${name} 耗时: ${Date.now() - start}ms`)
    return result
  }).catch(err => {
    console.log(`${name} 失败, 耗时: ${Date.now() - start}ms`)
    throw err
  })
}

// 使用
const data = await traceAsync('fetchUser', () => fetchUser(id))

9.2 使用 Clinic.js 分析

bash
# 安装
npm install -g clinic

# CPU 分析
clinic flame -- node app.js

# 内存分析
clinic heap-profiler -- node app.js

# 阻塞分析
clinic bubbleprof -- node app.js

9.3 async_hooks 追踪

typescript
import { createHook } from 'async_hooks'

const hook = createHook({
  init(asyncId, type, triggerAsyncId, resource) {
    console.log(`初始化: ${type} (asyncId=${asyncId}, trigger=${triggerAsyncId})`)
  },
  destroy(asyncId) {
    console.log(`销毁: asyncId=${asyncId}`)
  }
})

hook.enable()

十、总结

  • ✅ 深入理解 Event Loop 6 个阶段及微任务/宏任务
  • ✅ 掌握异步编程模式演进(回调 → Promise → async/await)
  • ✅ 实战并发控制(串行、并行、并发限制、异步队列)
  • ✅ 实现限流策略(时间窗口、令牌桶、漏桶)
  • ✅ 实现重试机制(简单重试、指数退避、可中断)
  • ✅ Worker Threads 并发编程与 Worker 池
  • ✅ 错误处理最佳实践与性能监控

Node.js 的异步编程是一门艺术,掌握这些技巧让你能编写出高效、可靠的代码。


相关阅读: