JavaScript 异步编程:从回调到 Promise 再到各种骚操作

这一篇聊聊 JavaScript 的异步编程:从最基础的回调函数,到 Promise,再到 async/await,
最后再讲一些实际开发中常用的模式和"骚操作"。

JavaScript 是单线程的,如果所有操作都是同步的,遇到耗时操作(网络请求、文件读取、定时器等)时,整个程序就会阻塞,用户界面会卡住。

异步编程让这些耗时操作在后台执行,不阻塞主线程,等操作完成后再通过回调、Promise 或 async/await 来处理结果。

在 Promise 和 async/await 出现之前,JavaScript 的异步主要靠回调函数:

 1// 典型的回调模式
 2setTimeout(() => {
 3  console.log('1秒后执行');
 4}, 1000);
 5
 6// Node.js 文件读取
 7fs.readFile('file.txt', 'utf8', (err, data) => {
 8  if (err) {
 9    console.error('读取失败:', err);
10    return;
11  }
12  console.log('文件内容:', data);
13});

回调函数的问题:

  • 回调地狱(Callback Hell):多层嵌套的回调让代码难以阅读和维护。
  • 错误处理分散:每个回调都要单独处理错误。
  • 难以组合:多个异步操作串行或并行执行时,代码会变得非常复杂。
 1// 回调地狱示例
 2fs.readFile('file1.txt', 'utf8', (err1, data1) => {
 3  if (err1) {
 4    console.error(err1);
 5    return;
 6  }
 7  fs.readFile('file2.txt', 'utf8', (err2, data2) => {
 8    if (err2) {
 9      console.error(err2);
10      return;
11    }
12    fs.writeFile('output.txt', data1 + data2, (err3) => {
13      if (err3) {
14        console.error(err3);
15        return;
16      }
17      console.log('完成');
18    });
19  });
20});

Promise 是 ES6 引入的异步编程解决方案,它代表一个异步操作的最终完成或失败。

 1// 创建一个 Promise
 2const promise = new Promise((resolve, reject) => {
 3  // 异步操作
 4  setTimeout(() => {
 5    const success = Math.random() > 0.5;
 6    if (success) {
 7      resolve('操作成功');
 8    } else {
 9      reject(new Error('操作失败'));
10    }
11  }, 1000);
12});
13
14// 使用 Promise
15promise
16  .then(result => {
17    console.log(result);
18  })
19  .catch(error => {
20    console.error(error);
21  });
  • pending(等待中):初始状态,既不是成功也不是失败。
  • fulfilled(已成功):操作成功完成。
  • rejected(已失败):操作失败。

Promise 的状态一旦改变就不会再变,只能从 pending 变为 fulfilled 或 rejected。

Promise 的 then 方法返回一个新的 Promise,可以链式调用:

 1fetch('/api/user')
 2  .then(response => response.json())
 3  .then(user => {
 4    console.log('用户信息:', user);
 5    return fetch(`/api/posts?userId=${user.id}`);
 6  })
 7  .then(response => response.json())
 8  .then(posts => {
 9    console.log('用户文章:', posts);
10  })
11  .catch(error => {
12    console.error('错误:', error);
13  });
 1const promise1 = fetch('/api/data1');
 2const promise2 = fetch('/api/data2');
 3const promise3 = fetch('/api/data3');
 4
 5Promise.all([promise1, promise2, promise3])
 6  .then(responses => {
 7    // 所有请求都成功
 8    return Promise.all(responses.map(r => r.json()));
 9  })
10  .then(results => {
11    console.log('所有数据:', results);
12  })
13  .catch(error => {
14    // 任何一个请求失败,整个 Promise.all 就会失败
15    console.error('有请求失败:', error);
16  });
 1Promise.allSettled([promise1, promise2, promise3])
 2  .then(results => {
 3    results.forEach((result, index) => {
 4      if (result.status === 'fulfilled') {
 5        console.log(`请求 ${index + 1} 成功:`, result.value);
 6      } else {
 7        console.log(`请求 ${index + 1} 失败:`, result.reason);
 8      }
 9    });
10  });
 1// 超时控制
 2const fetchWithTimeout = (url, timeout = 5000) => {
 3  return Promise.race([
 4    fetch(url),
 5    new Promise((_, reject) =>
 6      setTimeout(() => reject(new Error('请求超时')), timeout)
 7    )
 8  ]);
 9};
10
11fetchWithTimeout('/api/data', 3000)
12  .then(response => response.json())
13  .then(data => console.log(data))
14  .catch(error => console.error(error));
 1// 尝试多个数据源,返回第一个成功的
 2const sources = [
 3  fetch('/api/primary'),
 4  fetch('/api/backup1'),
 5  fetch('/api/backup2')
 6];
 7
 8Promise.any(sources)
 9  .then(response => response.json())
10  .then(data => {
11    console.log('获取到数据:', data);
12  })
13  .catch(error => {
14    // 所有请求都失败
15    console.error('所有数据源都失败:', error);
16  });

async/await 是 ES2017 引入的语法糖,基于 Promise,让异步代码的写法更接近同步代码。

 1// async 函数总是返回一个 Promise
 2async function fetchUser(id) {
 3  const response = await fetch(`/api/user/${id}`);
 4  const user = await response.json();
 5  return user;
 6}
 7
 8// 使用
 9fetchUser(1)
10  .then(user => console.log(user))
11  .catch(error => console.error(error));
 1// 使用 try/catch
 2async function fetchUser(id) {
 3  try {
 4    const response = await fetch(`/api/user/${id}`);
 5    if (!response.ok) {
 6      throw new Error(`HTTP error! status: ${response.status}`);
 7    }
 8    const user = await response.json();
 9    return user;
10  } catch (error) {
11    console.error('获取用户失败:', error);
12    throw error; // 重新抛出错误
13  }
14}
 1// 错误:串行执行(慢)
 2async function fetchAllUsers() {
 3  const user1 = await fetch('/api/user/1');
 4  const user2 = await fetch('/api/user/2');
 5  const user3 = await fetch('/api/user/3');
 6  return [user1, user2, user3];
 7}
 8
 9// 正确:并行执行(快)
10async function fetchAllUsers() {
11  const [user1, user2, user3] = await Promise.all([
12    fetch('/api/user/1'),
13    fetch('/api/user/2'),
14    fetch('/api/user/3')
15  ]);
16  return [user1, user2, user3];
17}
 1async function fetchWithRetry(url, maxRetries = 3) {
 2  for (let i = 0; i < maxRetries; i++) {
 3    try {
 4      const response = await fetch(url);
 5      if (response.ok) {
 6        return await response.json();
 7      }
 8      throw new Error(`HTTP ${response.status}`);
 9    } catch (error) {
10      if (i === maxRetries - 1) throw error;
11      console.log(`重试 ${i + 1}/${maxRetries}...`);
12      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
13    }
14  }
15}
 1async function limitConcurrency(tasks, limit) {
 2  const results = [];
 3  const executing = [];
 4
 5  for (const task of tasks) {
 6    const promise = task().then(result => {
 7      executing.splice(executing.indexOf(promise), 1);
 8      return result;
 9    });
10
11    results.push(promise);
12    executing.push(promise);
13
14    if (executing.length >= limit) {
15      await Promise.race(executing);
16    }
17  }
18
19  return Promise.all(results);
20}
21
22// 使用
23const tasks = Array.from({ length: 10 }, (_, i) => 
24  () => fetch(`/api/data/${i}`)
25);
26
27limitConcurrency(tasks, 3).then(results => {
28  console.log('完成:', results);
29});
 1// 防抖:延迟执行,如果再次触发则重新计时
 2function debounce(func, delay) {
 3  let timeoutId;
 4  return function (...args) {
 5    clearTimeout(timeoutId);
 6    timeoutId = setTimeout(() => func.apply(this, args), delay);
 7  };
 8}
 9
10// 节流:限制执行频率
11function throttle(func, delay) {
12  let lastCall = 0;
13  return function (...args) {
14    const now = Date.now();
15    if (now - lastCall >= delay) {
16      lastCall = now;
17      func.apply(this, args);
18    }
19  };
20}
21
22// 异步版本的防抖
23function asyncDebounce(func, delay) {
24  let timeoutId;
25  let lastPromise;
26  return function (...args) {
27    return new Promise((resolve, reject) => {
28      clearTimeout(timeoutId);
29      timeoutId = setTimeout(async () => {
30        try {
31          const result = await func.apply(this, args);
32          resolve(result);
33        } catch (error) {
34          reject(error);
35        }
36      }, delay);
37    });
38  };
39}
 1function withTimeout(promise, timeout, errorMessage = '操作超时') {
 2  return Promise.race([
 3    promise,
 4    new Promise((_, reject) =>
 5      setTimeout(() => reject(new Error(errorMessage)), timeout)
 6    )
 7  ]);
 8}
 9
10// 使用
11const data = await withTimeout(
12  fetch('/api/slow-endpoint'),
13  5000,
14  '请求超时,请稍后重试'
15);
 1function cancellablePromise(executor) {
 2  let cancel;
 3  const promise = new Promise((resolve, reject) => {
 4    cancel = () => {
 5      reject(new Error('操作已取消'));
 6    };
 7    executor(resolve, reject);
 8  });
 9  return { promise, cancel };
10}
11
12// 使用
13const { promise, cancel } = cancellablePromise((resolve, reject) => {
14  setTimeout(() => resolve('完成'), 5000);
15});
16
17// 3秒后取消
18setTimeout(() => cancel(), 3000);
19
20promise
21  .then(result => console.log(result))
22  .catch(error => console.error(error)); // "操作已取消"
 1async function batchProcess(items, processor, batchSize = 5) {
 2  const results = [];
 3  for (let i = 0; i < items.length; i += batchSize) {
 4    const batch = items.slice(i, i + batchSize);
 5    const batchResults = await Promise.all(
 6      batch.map(item => processor(item))
 7    );
 8    results.push(...batchResults);
 9  }
10  return results;
11}
12
13// 使用
14const items = Array.from({ length: 100 }, (_, i) => i);
15const results = await batchProcess(
16  items,
17  async (item) => {
18    // 处理每个项目
19    return await processItem(item);
20  },
21  10 // 每批处理10个
22);
 1async function sequentialProcess(items, processor) {
 2  const results = [];
 3  for (const item of items) {
 4    const result = await processor(item);
 5    results.push(result);
 6  }
 7  return results;
 8}
 9
10// 使用
11const items = [1, 2, 3, 4, 5];
12const results = await sequentialProcess(
13  items,
14  async (item) => {
15    console.log(`处理 ${item}...`);
16    return await processItem(item);
17  }
18);
 1async function conditionalExecute(condition, asyncFn) {
 2  if (await condition()) {
 3    return await asyncFn();
 4  }
 5  return null;
 6}
 7
 8// 使用
 9const result = await conditionalExecute(
10  async () => {
11    const cache = await getCache();
12    return !cache; // 缓存不存在时才执行
13  },
14  async () => {
15    return await fetchData();
16  }
17);
 1function cachePromise(fn) {
 2  const cache = new Map();
 3  return function (...args) {
 4    const key = JSON.stringify(args);
 5    if (cache.has(key)) {
 6      return cache.get(key);
 7    }
 8    const promise = fn.apply(this, args).finally(() => {
 9      // 可选:设置缓存过期时间
10      // setTimeout(() => cache.delete(key), 60000);
11    });
12    cache.set(key, promise);
13    return promise;
14  };
15}
16
17// 使用
18const cachedFetch = cachePromise(fetch);
19
20// 多次调用相同的 URL,只会发起一次请求
21cachedFetch('/api/data');
22cachedFetch('/api/data'); // 使用缓存的 Promise
 1class PromiseQueue {
 2  constructor(concurrency = 1) {
 3    this.concurrency = concurrency;
 4    this.running = 0;
 5    this.queue = [];
 6  }
 7
 8  add(task) {
 9    return new Promise((resolve, reject) => {
10      this.queue.push({
11        task,
12        resolve,
13        reject
14      });
15      this.process();
16    });
17  }
18
19  async process() {
20    if (this.running >= this.concurrency || this.queue.length === 0) {
21      return;
22    }
23
24    this.running++;
25    const { task, resolve, reject } = this.queue.shift();
26
27    try {
28      const result = await task();
29      resolve(result);
30    } catch (error) {
31      reject(error);
32    } finally {
33      this.running--;
34      this.process();
35    }
36  }
37}
38
39// 使用
40const queue = new PromiseQueue(2); // 最多同时执行2个任务
41
42queue.add(() => fetch('/api/1'));
43queue.add(() => fetch('/api/2'));
44queue.add(() => fetch('/api/3')); // 等待前面的任务完成
 1async function* asyncGenerator() {
 2  for (let i = 0; i < 5; i++) {
 3    await new Promise(resolve => setTimeout(resolve, 1000));
 4    yield i;
 5  }
 6}
 7
 8// 使用 for await...of
 9(async () => {
10  for await (const value of asyncGenerator()) {
11    console.log(value);
12  }
13})();
 1async function fetchWithExponentialBackoff(url, maxRetries = 5) {
 2  for (let i = 0; i < maxRetries; i++) {
 3    try {
 4      const response = await fetch(url);
 5      if (response.ok) {
 6        return await response.json();
 7      }
 8      throw new Error(`HTTP ${response.status}`);
 9    } catch (error) {
10      if (i === maxRetries - 1) throw error;
11      
12      // 指数退避:1s, 2s, 4s, 8s, 16s
13      const delay = Math.pow(2, i) * 1000;
14      console.log(`重试 ${i + 1}/${maxRetries},等待 ${delay}ms...`);
15      await new Promise(resolve => setTimeout(resolve, delay));
16    }
17  }
18}
 1// 不好的做法
 2async function fetchData() {
 3  const data = await fetch('/api/data');
 4  return data.json();
 5}
 6
 7// 好的做法
 8async function fetchData() {
 9  try {
10    const response = await fetch('/api/data');
11    if (!response.ok) {
12      throw new Error(`HTTP error! status: ${response.status}`);
13    }
14    return await response.json();
15  } catch (error) {
16    console.error('获取数据失败:', error);
17    throw error; // 或者返回默认值
18  }
19}
 1// 不好的做法:串行执行
 2for (const item of items) {
 3  await processItem(item);
 4}
 5
 6// 好的做法:并行执行
 7await Promise.all(items.map(item => processItem(item)));
 8
 9// 或者需要限制并发时
10await limitConcurrency(
11  items.map(item => () => processItem(item)),
12  5
13);
 1// 当某些操作失败不应该影响其他操作时
 2const results = await Promise.allSettled([
 3  fetch('/api/data1'),
 4  fetch('/api/data2'),
 5  fetch('/api/data3')
 6]);
 7
 8const successful = results
 9  .filter(r => r.status === 'fulfilled')
10  .map(r => r.value);
11
12const failed = results
13  .filter(r => r.status === 'rejected')
14  .map(r => r.reason);
 1// 简单的情况用 async/await
 2async function getUser(id) {
 3  const response = await fetch(`/api/user/${id}`);
 4  return response.json();
 5}
 6
 7// 需要并行执行时用 Promise.all
 8const [user, posts] = await Promise.all([
 9  fetch('/api/user/1').then(r => r.json()),
10  fetch('/api/posts').then(r => r.json())
11]);
12
13// 需要第一个结果时用 Promise.race
14const firstResult = await Promise.race([
15  fetch('/api/source1'),
16  fetch('/api/source2')
17]);

JavaScript 的异步编程从回调函数到 Promise,再到 async/await,不断演进:

  • 回调函数:最原始的方式,容易产生回调地狱。
  • Promise:提供了链式调用和统一的错误处理,是异步编程的基础。
  • async/await:让异步代码看起来像同步代码,更易读易写。

在实际开发中,掌握这些常用操作和模式:

  • 并发控制、重试机制、超时处理
  • 防抖节流、批量处理、顺序执行
  • Promise 队列、缓存、取消机制
  • 错误处理和最佳实践

理解这些模式,可以写出更健壮、更高效的异步代码。