异步编程

JavaScript是单线程语言,异步编程是处理耗时操作(如网络请求、文件I/O等)的关键技术。

回调函数

回调函数是异步编程的传统方式,它是一个作为参数传递给另一个函数的函数:

javascript
// 示例:读取文件
const fs = require('fs');

fs.readFile('file.txt', 'utf8', (err, data) => {
  if (err) {
    console.error('读取文件失败:', err);
    return;
  }
  console.log('文件内容:', data);
});

console.log('读取文件操作已发起');

回调地狱

当有多个嵌套的异步操作时,会导致代码难以阅读和维护,这被称为回调地狱:

javascript
fs.readFile('file1.txt', 'utf8', (err, data1) => {
  if (err) throw err;
  fs.readFile('file2.txt', 'utf8', (err, data2) => {
    if (err) throw err;
    fs.writeFile('output.txt', data1 + data2, (err) => {
      if (err) throw err;
      console.log('文件已合并');
    });
  });
});

Promise

Promise是ES6引入的解决异步编程的方案,它代表一个异步操作的最终完成(或失败)及其结果值:

Promise的创建

javascript
const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Math.random() > 0.5) {
        resolve("数据获取成功");
      } else {
        reject("数据获取失败");
      }
    }, 1000);
  });
};

Promise的使用

javascript
fetchData()
  .then(data => {
    console.log(data);
    return "处理后的数据";
  })
  .then(processedData => {
    console.log(processedData);
  })
  .catch(error => {
    console.error(error);
  })
  .finally(() => {
    console.log("操作完成");
  });

Promise的静态方法

Promise.all

等待所有Promise完成:

javascript
const promises = [
  fetch('https://api.example.com/data1'),
  fetch('https://api.example.com/data2'),
  fetch('https://api.example.com/data3')
];

Promise.all(promises)
  .then(responses => {
    return Promise.all(responses.map(res => res.json()));
  })
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error(error);
  });

Promise.race

等待第一个完成的Promise:

javascript
const promise1 = new Promise(resolve => setTimeout(resolve, 1000, 'one'));
const promise2 = new Promise(resolve => setTimeout(resolve, 500, 'two'));

Promise.race([promise1, promise2])
  .then(result => {
    console.log(result); // 'two'
  });

Promise.allSettled

等待所有Promise完成,无论成功或失败:

javascript
const promises = [
  Promise.resolve(1),
  Promise.reject('错误'),
  Promise.resolve(3)
];

Promise.allSettled(promises)
  .then(results => {
    results.forEach(result => {
      if (result.status === 'fulfilled') {
        console.log('成功:', result.value);
      } else {
        console.log('失败:', result.reason);
      }
    });
  });

Promise.any

等待第一个成功的Promise:

javascript
const promises = [
  Promise.reject('错误1'),
  Promise.resolve(2),
  Promise.reject('错误3')
];

Promise.any(promises)
  .then(result => {
    console.log('成功:', result); // 2
  })
  .catch(error => {
    console.error('所有Promise都失败了:', error);
  });

async/await

async/await是ES2017引入的语法糖,基于Promise,使异步代码看起来像同步代码:

async函数

async关键字用于声明异步函数,异步函数总是返回一个Promise:

javascript
async function fetchData() {
  return "数据";
  // 相当于 return Promise.resolve("数据");
}

fetchData().then(data => console.log(data)); // "数据"

await关键字

await关键字用于等待一个Promise完成,只能在async函数内部使用:

javascript
async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
    return data;
  } catch (error) {
    console.error('数据获取失败:', error);
  }
}

fetchData();

并行执行

javascript
async function fetchMultipleData() {
  const [data1, data2] = await Promise.all([
    fetch('https://api.example.com/data1').then(res => res.json()),
    fetch('https://api.example.com/data2').then(res => res.json())
  ]);
  
  console.log('数据1:', data1);
  console.log('数据2:', data2);
}

fetchMultipleData();

事件循环

理解事件循环对于掌握异步编程至关重要:

执行栈

JavaScript引擎使用执行栈来管理函数调用。

任务队列

异步操作完成后,其回调函数被放入任务队列:

  • 宏任务(Macro Task):setTimeout, setInterval, setImmediate, I/O, UI渲染
  • 微任务(Micro Task):Promise, process.nextTick, MutationObserver

事件循环流程

  1. 执行执行栈中的同步代码
  2. 执行所有微任务队列中的任务
  3. 渲染UI
  4. 从宏任务队列中取出一个任务执行
  5. 重复步骤2-4
javascript
console.log('1');

setTimeout(() => {
  console.log('2');
}, 0);

Promise.resolve().then(() => {
  console.log('3');
});

console.log('4');

// 输出顺序:1, 4, 3, 2

异步编程最佳实践

  1. 使用async/await:使代码更易读和维护
  2. 处理错误:使用try/catch或.catch()处理异步操作的错误
  3. 避免阻塞主线程:将耗时操作放在Web Worker或Node.js的子进程中
  4. 限制并发请求:避免同时发起过多网络请求
  5. 使用Promise.all处理并行操作:提高性能

学习资源


继续学习:DOM操作

最后更新:2026-02-08