跳转到主要内容

JavaScript 异步编程深度解析

什么是异步编程

异步编程是一种编程模式,允许程序在等待某些操作(如网络请求、文件读取)完成的同时继续执行其他代码。这对于构建高性能的用户界面至关重要。

JavaScript 是单线程的

JavaScript 运行在单个线程上,这意味着它一次只能执行一个任务。但是,通过异步编程,JavaScript 可以在等待耗时操作时处理其他任务。

事件循环(Event Loop)

事件循环是 JavaScript 实现异步的核心机制:

console.log('1');

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

console.log('3');

// 输出顺序:1 -> 3 -> 2

执行顺序

  1. 同步任务:在主线程上执行
  2. 微任务:Promise.then、MutationObserver 等
  3. 宏任务:setTimeout、setInterval、I/O 等
console.log('start');

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

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

console.log('end');

// 输出:start -> end -> promise -> timeout

回调函数(Callbacks)

回调是最原始的异步处理方式:

function fetchData(callback) {
  setTimeout(() => {
    callback('数据加载完成');
  }, 1000);
}

fetchData((data) => {
  console.log(data);
});

回调地狱

过多的嵌套回调会导致”回调地狱”:

// ❌ 不推荐
getData((data) => {
  processData(data, (processed) => {
    saveData(processed, (saved) => {
      // 继续嵌套...
    });
  });
});

Promise

Promise 是对回调的改进,提供了更好的错误处理和链式调用:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('数据加载完成');
  }, 1000);
});

promise
  .then(data => {
    console.log(data);
    return data.toUpperCase();
  })
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error(error);
  });

Promise 链

Promise 支持链式调用:

fetchUser(userId)
  .then(user => fetchPosts(user.id))
  .then(posts => processPosts(posts))
  .then(processed => displayPosts(processed))
  .catch(error => handleError(error));

Promise.all

并行执行多个 Promise:

const promises = [
  fetch('/api/users'),
  fetch('/api/posts'),
  fetch('/api/comments'),
];

Promise.all(promises)
  .then(responses => Promise.all(responses.map(r => r.json())))
  .then(data => console.log(data));

Promise.race

使用最先完成的 Promise:

const timeout = new Promise((resolve, reject) => {
  setTimeout(() => reject('请求超时'), 5000);
});

Promise.race([fetch('/api/data'), timeout])
  .then(data => console.log(data))
  .catch(error => console.error(error));

async/await

async/await 是 Promise 的语法糖,让异步代码看起来像同步代码:

async function fetchData() {
  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error(error);
  }
}

错误处理

使用 try-catch 处理错误:

async function handleRequest() {
  try {
    const user = await fetchUser(userId);
    const posts = await fetchPosts(user.id);
    return posts;
  } catch (error) {
    console.error('请求失败:', error);
    throw error; // 可以选择重新抛出或处理错误
  }
}

并行执行

使用 Promise.all + async/await:

async function fetchAllData() {
  const [users, posts, comments] = await Promise.all([
    fetch('/api/users').then(r => r.json()),
    fetch('/api/posts').then(r => r.json()),
    fetch('/api/comments').then(r => r.json()),
  ]);

  return { users, posts, comments };
}

实际应用场景

1. 数据获取

async function loadUserProfile(userId) {
  const user = await fetch(`/api/users/${userId}`).then(r => r.json());
  const posts = await fetch(`/api/users/${userId}/posts`).then(r => r.json());
  return { user, posts };
}

2. 表单提交

async function submitForm(formData) {
  try {
    const response = await fetch('/api/submit', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(formData),
    });

    if (!response.ok) {
      throw new Error('提交失败');
    }

    const result = await response.json();
    return result;
  } catch (error) {
    console.error(error);
    alert('提交失败,请重试');
  }
}

3. 图片懒加载

async function loadImage(src) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => resolve(img);
    img.onerror = reject;
    img.src = src;
  });
}

最佳实践

1. 始终处理错误

// ❌ 错误示例
fetchData().then(data => console.log(data));

// ✅ 正确示例
fetchData()
  .then(data => console.log(data))
  .catch(error => console.error(error));

2. 避免在循环中使用 async/await

// ❌ 串行执行,慢
async function processItems(items) {
  for (const item of items) {
    await processItem(item); // 每次都等待
  }
}

// ✅ 并行执行,快
async function processItems(items) {
  return Promise.all(items.map(item => processItem(item)));
}

3. 使用 AbortController 取消请求

const controller = new AbortController();

async function fetchData() {
  try {
    const response = await fetch('/api/data', {
      signal: controller.signal,
    });
    return await response.json();
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('请求已取消');
    }
    throw error;
  }
}

// 取消请求
controller.abort();

总结

JavaScript 异步编程是前端开发的核心概念:

  • 🔄 事件循环:理解异步执行的基础
  • 📞 回调:最初的异步处理方式
  • 🎯 Promise:改进的异步处理,支持链式调用
  • async/await:最优雅的异步语法
  • 🛡️ 错误处理:始终处理可能的错误

掌握异步编程将帮助你构建更快、更可靠的 JavaScript 应用!