并行执行的模拟实现

异步编程的本质

在现代JavaScript开发中,异步编程已成为处理I/O操作、网络请求和耗时任务的核心范式。JavaScript作为单线程语言,通过事件循环机制实现了非阻塞的异步行为。然而,真正的"并行"执行在纯JavaScript环境中是不存在的,但我们可以通过巧妙的设计来模拟并行执行的效果。

常见的异步模式

1. 回调函数

回调是最基础的异步模式,但容易导致"回调地狱":

javascript 复制代码
fs.readFile('file1.txt', (err, data1) => {
  if (err) throw err;
  fs.readFile('file2.txt', (err, data2) => {
    if (err) throw err;
    // 处理data1和data2
  });
});

2. Promise

Promise提供了更优雅的链式调用:

javascript 复制代码
readFilePromise('file1.txt')
  .then(data1 => readFilePromise('file2.txt'))
  .then(data2 => {
    // 处理data
  })
  .catch(err => console.error(err));

3. Async/Await

ES2017引入的语法糖,使异步代码看起来像同步:

javascript 复制代码
async function processFiles() {
  try {
    const data1 = await readFilePromise('file1.txt');
    const data2 = await readFilePromise('file2.txt');
    // 处理data
  } catch (err) {
    console.error(err);
  }
}

模拟并行执行

虽然JavaScript是单线程的,但我们可以利用事件循环和异步API来模拟并行执行的效果。

1. Promise.all实现并行

javascript 复制代码
async function parallelTasks() {
  const [result1, result2] = await Promise.all([
    fetch('https://api.example.com/data1'),
    fetch('https://api.example.com/data2')
  ]);
  
  // 两个请求同时发出,当都完成时继续执行
  console.log(result1, result2);
}

2. 使用Promise.race处理竞态条件

javascript 复制代码
async function fetchWithTimeout(url, timeout = 5000) {
  const fetchPromise = fetch(url);
  const timeoutPromise = new Promise((_, reject) => 
    setTimeout(() => reject(new Error('请求超时')), timeout)
  );
  
  return await Promise.race([fetchPromise, timeoutPromise]);
}

3. Worker线程实现真正并行

Node.js和现代浏览器支持Worker API,可以在独立线程中执行代码:

javascript 复制代码
// 主线程
const worker = new Worker('worker.js');
worker.postMessage({ task: 'heavyComputation' });
worker.onmessage = (e) => {
  console.log('收到结果:', e.data);
};

// worker.js
self.onmessage = (e) => {
  if (e.data.task === 'heavyComputation') {
    const result = performHeavyComputation();
    self.postMessage(result);
  }
};

高级并行模式

1. 限制并发数的并行执行

javascript 复制代码
async function parallelWithLimit(tasks, limit) {
  const results = [];
  const executing = new Set();
  
  for (const task of tasks) {
    const p = task().then(res => {
      executing.delete(p);
      return res;
    });
    
    executing.add(p);
    results.push(p);
    
    if (executing.size >= limit) {
      await Promise.race(executing);
    }
  }
  
  return Promise.all(results);
}

2. 使用async-pool库简化并行控制

javascript 复制代码
import asyncPool from 'tiny-async-pool';

const results = await asyncPool(
  3, // 并发数
  [1, 2, 3, 4, 5], // 输入数组
  async (item) => {
    // 对每个item执行异步操作
    return await processItem(item);
  }
);

性能考量与最佳实践

  1. 避免过度并行化:并行任务过多可能导致内存压力或API速率限制
  2. 错误处理:确保捕获所有可能的拒绝,避免未处理的Promise
  3. 资源清理:长时间运行的并行任务要注意及时释放资源
  4. 性能监控:使用性能API测量并行执行的效率
javascript 复制代码
async function monitoredParallel() {
  const start = performance.now();
  
  await parallelTasks();
  
  const duration = performance.now() - start;
  console.log(`并行执行耗时: ${duration}ms`);
}

总结

虽然JavaScript的单线程本质限制了真正的并行执行,但通过Promise、async/await、Web Workers等现代API,我们可以有效地模拟并行行为,显著提升应用性能。理解这些模式的底层机制和适用场景,是成为高级JavaScript开发者的关键一步。