Promise 与 async/await:从回调地狱到优雅异步的演进之路

0 阅读10分钟

在学习 JavaScript 异步编程的过程中,我一直有个困惑:为什么异步方案要经历从 Callback 到 Promise 再到 async/await 的演进?每次被问到"Promise 解决了什么问题"时,我总觉得自己的回答流于表面。直到最近深入研究了 Promise 的设计思想和 async/await 的实现原理,才开始理解这些看似简单的 API 背后隐藏的深刻智慧。这篇文章是我的学习总结,希望能和你一起探索异步编程的本质。

问题的起源:回调地狱到底"地狱"在哪?

回调函数的基本形式

在 Promise 出现之前,JavaScript 处理异步操作主要依赖回调函数:

// 环境:Node.js / 浏览器
// 场景:传统回调函数处理异步

function fetchUser(userId, callback) {
  setTimeout(() => {
    const user = { id: userId, name: 'Alice' };
    callback(null, user);
  }, 100);
}

fetchUser(1, (error, user) => {
  if (error) {
    console.error(error);
    return;
  }
  console.log(user);
});

这看起来还挺简单的,那为什么会有"回调地狱"这个说法呢?

回调地狱的真实面貌

当业务逻辑变复杂,需要多个异步操作依次执行时,问题就来了:

// 环境:Node.js / 浏览器
// 场景:多层嵌套的回调,形成"金字塔"结构

fetchUser(1, (err, user) => {
  if (err) {
    console.error('Failed to fetch user:', err);
    return;
  }
  
  fetchUserPosts(user.id, (err, posts) => {
    if (err) {
      console.error('Failed to fetch posts:', err);
      return;
    }
    
    fetchPostComments(posts[0].id, (err, comments) => {
      if (err) {
        console.error('Failed to fetch comments:', err);
        return;
      }
      
      console.log('Comments:', comments);
      // 如果还有更多层级...代码会继续向右缩进
    });
  });
});

回调地狱的本质问题

经过研究,我发现"回调地狱"的问题不仅仅是代码难看,更深层的问题包括:

1. 控制流不直观

人类的思维是线性的:"先做 A,再做 B,最后做 C"。但回调函数的嵌套破坏了这种线性思维,代码的执行顺序和阅读顺序不一致。

2. 错误处理重复且容易遗漏

每一层回调都需要单独处理错误,很容易漏掉某个 if (err) 判断。而且错误处理逻辑分散在各处,难以统一管理。

3. 难以组合和复用

如果想要并行执行多个异步操作,或者在某些条件下跳过某些步骤,用回调函数很难优雅地实现。

4. 信任问题

当你把回调函数传给第三方库时,你无法控制这个回调会被调用几次、什么时候调用,甚至是否会被调用。

Promise:一种全新的异步解决方案

Promise 的核心概念

Promise 是一个表示异步操作最终结果的对象。我的理解是,Promise 把"未来的值"封装成了一个对象,让你可以用同步的方式操作异步的结果。

// 环境:浏览器 / Node.js 18+
// 场景:Promise 基础用法

// 创建一个 Promise
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const success = true;
    if (success) {
      resolve('Operation succeeded');
    } else {
      reject('Operation failed');
    }
  }, 1000);
});

// 使用 Promise
promise
  .then(result => {
    console.log(result); // 'Operation succeeded'
  })
  .catch(error => {
    console.error(error);
  });

Promise 的三个状态

Promise 有三个状态,且状态转换是单向不可逆的:

stateDiagram-v2
    [*] --> Pending
    Pending --> Fulfilled: resolve()
    Pending --> Rejected: reject()
    Fulfilled --> [*]
    Rejected --> [*]
  • Pending(待定) :初始状态,既没有成功也没有失败
  • Fulfilled(已兑现) :操作成功完成
  • Rejected(已拒绝) :操作失败

为什么状态要设计成不可逆的?

这个设计有深刻的考量:

  1. 可预测性:一旦 Promise 确定了结果,这个结果就永远不会改变。你可以安全地多次调用 .then(),每次都会得到相同的结果。
  2. 避免竞态条件:如果状态可以反复改变,就会出现"resolve 之后又 reject"的情况,导致不可预测的行为。
  3. 符合函数式编程思想:Promise 是 immutable(不可变)的,这让异步操作的结果可以像纯函数一样被推理和组合。
// 环境:浏览器 / Node.js 18+
// 场景:Promise 状态不可逆的演示

const promise = new Promise((resolve, reject) => {
  resolve('first value');
  resolve('second value'); // 无效,状态已经是 fulfilled
  reject('error');         // 无效,状态已经是 fulfilled
});

promise.then(value => {
  console.log(value); // 输出:'first value'
});

Promise 链式调用的魔法

Promise 最强大的特性之一是链式调用。每个 .then() 都会返回一个新的 Promise

// 环境:浏览器 / Node.js 18+
// 场景:Promise 链式调用

fetch('/api/user')
  .then(response => response.json())
  .then(user => fetch(`/api/posts?userId=${user.id}`))
  .then(response => response.json())
  .then(posts => {
    console.log('User posts:', posts);
  })
  .catch(error => {
    console.error('Error in chain:', error);
  });

这段代码展示了 Promise 如何解决回调地狱的问题:

  1. 扁平化结构:不再需要嵌套,代码向下展开而不是向右缩进
  2. 统一错误处理:只需要一个 .catch() 就能捕获链条中任何一步的错误
  3. 清晰的控制流:从上到下阅读,符合人类的线性思维

关键理解:每个 .then() 返回的是新的 Promise

// 环境:浏览器 / Node.js 18+
// 场景:理解 .then() 返回新 Promise

const promise1 = Promise.resolve(1);
const promise2 = promise1.then(value => value + 1);
const promise3 = promise2.then(value => value + 1);

console.log(promise1 === promise2); // false
console.log(promise2 === promise3); // false

// 每个 .then() 都创建了新的 Promise

Promise 的错误处理机制

Promise 的错误处理有一个很优雅的设计:错误会沿着链条"冒泡",直到被捕获。

// 环境:浏览器 / Node.js 18+
// 场景:Promise 错误冒泡

Promise.resolve()
  .then(() => {
    console.log('Step 1');
    return 'result 1';
  })
  .then(() => {
    console.log('Step 2');
    throw new Error('Something went wrong');
  })
  .then(() => {
    console.log('Step 3'); // 不会执行
  })
  .catch(error => {
    console.error('Caught:', error.message); // 捕获错误
  })
  .then(() => {
    console.log('Step 4'); // 会继续执行
  });

// 输出:
// Step 1
// Step 2
// Caught: Something went wrong
// Step 4

.catch() vs .then(null, onRejected) 的区别

// 环境:浏览器 / Node.js 18+
// 场景:两种错误处理方式的区别

// 方式 1:使用 .catch()
Promise.resolve()
  .then(() => {
    throw new Error('Error in then');
  })
  .catch(error => {
    console.log('Caught by catch:', error.message);
  });

// 方式 2:使用 .then(onFulfilled, onRejected)
Promise.resolve()
  .then(
    () => {
      throw new Error('Error in then');
    },
    error => {
      // 这个回调捕获不到上面 then 中的错误!
      console.log('Caught by then:', error.message);
    }
  );

// 输出:
// Caught by catch: Error in then
// Uncaught Error: Error in then

区别在于:.then(onFulfilled, onRejected)onRejected 只能捕获前面 Promise 的错误,无法捕获 onFulfilled 中抛出的错误。而 .catch() 可以捕获前面链条中任何位置的错误。

Promise 工具方法:并发控制的利器

Promise.all:全部成功才成功

// 环境:浏览器 / Node.js 18+
// 场景:并行执行多个请求,等待全部完成

const promise1 = fetch('/api/users');
const promise2 = fetch('/api/posts');
const promise3 = fetch('/api/comments');

Promise.all([promise1, promise2, promise3])
  .then(([users, posts, comments]) => {
    console.log('All data loaded:', { users, posts, comments });
  })
  .catch(error => {
    console.error('At least one request failed:', error);
  });

特点:

  • 所有 Promise 都成功时,返回所有结果的数组
  • 只要有一个失败,立即 reject,其他 Promise 继续执行但结果被忽略
  • 适用场景:多个请求相互依赖,缺一不可

Promise.allSettled:不管成败,全部等待

// 环境:浏览器 / Node.js 18+
// 场景:批量操作,即使部分失败也要知道结果

const promises = [
  fetch('/api/user/1'),
  fetch('/api/user/2'),
  fetch('/api/user/999'), // 假设这个会失败
];

Promise.allSettled(promises)
  .then(results => {
    results.forEach((result, index) => {
      if (result.status === 'fulfilled') {
        console.log(`Promise ${index} succeeded:`, result.value);
      } else {
        console.log(`Promise ${index} failed:`, result.reason);
      }
    });
  });

// 输出:
// Promise 0 succeeded: [Response object]
// Promise 1 succeeded: [Response object]
// Promise 2 failed: [Error object]

特点:

  • 等待所有 Promise 完成(无论成功或失败)
  • 返回数组,每个元素是 { status: 'fulfilled'|'rejected', value|reason }
  • 适用场景:批量操作,需要知道每个操作的结果

Promise.race:谁快听谁的

// 环境:浏览器 / Node.js 18+
// 场景:请求超时控制

function fetchWithTimeout(url, timeout = 5000) {
  const fetchPromise = fetch(url);
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error('Request timeout')), timeout);
  });
  
  return Promise.race([fetchPromise, timeoutPromise]);
}

fetchWithTimeout('/api/slow-endpoint', 3000)
  .then(response => console.log('Success:', response))
  .catch(error => console.error('Failed:', error.message));

特点:

  • 第一个完成的 Promise 决定结果
  • 适用场景:超时控制、取最快响应

Promise.any:有一个成功就成功

// 环境:浏览器 / Node.js 18+
// 场景:请求多个镜像服务器,使用最快的响应

const mirrors = [
  fetch('https://mirror1.com/api/data'),
  fetch('https://mirror2.com/api/data'),
  fetch('https://mirror3.com/api/data'),
];

Promise.any(mirrors)
  .then(response => {
    console.log('Got response from fastest mirror:', response);
  })
  .catch(error => {
    console.error('All mirrors failed:', error);
  });

特点:

  • 只要有一个成功就返回
  • 所有都失败才 reject,返回 AggregateError
  • 适用场景:多个备选方案,取最快成功的

async/await:Promise 的语法糖

async/await 的本质

async/await 让异步代码看起来像同步代码,但它的本质是 Promise + Generator 的语法糖。

// 环境:浏览器 / Node.js 18+
// 场景:async/await 基础用法

// async 函数总是返回 Promise
async function fetchUser(id) {
  return { id, name: 'Alice' };
}

// 等价于
function fetchUser(id) {
  return Promise.resolve({ id, name: 'Alice' });
}

// 调用
fetchUser(1).then(user => console.log(user));

await 的执行机制

await 做了三件事:

  1. 等待 Promise 完成
  2. 返回 Promise 的结果(如果成功)
  3. 将 await 后面的代码转换成 .then() 回调
// 环境:浏览器 / Node.js 18+
// 场景:await 的执行机制

async function example() {
  console.log('1');
  const result = await Promise.resolve('2');
  console.log(result);
  console.log('3');
}

// 等价转换
function example() {
  console.log('1');
  return Promise.resolve('2').then(result => {
    console.log(result);
    console.log('3');
  });
}

example();
console.log('4');

// 输出:1 → 4 → 2 → 3

为什么 await 后面的代码会变成微任务?

因为它被转换成了 .then() 回调,而 .then() 回调会被放入微任务队列。这就是为什么 console.log('4') 会在 console.log(result) 之前执行。

async/await 的错误处理

async/await 让我们可以用传统的 try-catch 处理异步错误:

// 环境:浏览器 / Node.js 18+
// 场景:async/await 错误处理

async function fetchData() {
  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Failed to fetch data:', error);
    throw error; // 可以选择重新抛出或返回默认值
  }
}

但是,每个 await 都写 try-catch 很繁琐。有更优雅的方式吗?

一个常见的模式是创建一个辅助函数:

// 环境:浏览器 / Node.js 18+
// 场景:优雅的错误处理辅助函数

function to(promise) {
  return promise
    .then(data => [null, data])
    .catch(error => [error, null]);
}

// 使用
async function loadUser() {
  const [error, user] = await to(fetch('/api/user'));
  
  if (error) {
    console.error('Failed to load user:', error);
    return;
  }
  
  console.log('User loaded:', user);
}

这个模式受到 Go 语言错误处理的启发,让错误处理更加显式和可控。

实际应用场景

场景 1:串行 vs 并行

这是一个容易踩坑的地方。

// 环境:浏览器 / Node.js 18+
// 场景:对比串行和并行的执行时间

// ❌ 串行执行:总耗时 = 所有请求耗时之和
async function serialFetch() {
  const start = Date.now();
  
  const user = await fetch('/api/user');      // 等待 100ms
  const posts = await fetch('/api/posts');    // 等待 100ms
  const comments = await fetch('/api/comments'); // 等待 100ms
  
  console.log('Serial time:', Date.now() - start); // 约 300ms
}

// ✅ 并行执行:总耗时 = 最慢的请求耗时
async function parallelFetch() {
  const start = Date.now();
  
  const [user, posts, comments] = await Promise.all([
    fetch('/api/user'),
    fetch('/api/posts'),
    fetch('/api/comments')
  ]);
  
  console.log('Parallel time:', Date.now() - start); // 约 100ms
}

何时选择串行?何时选择并行?

  • 串行:当后面的请求依赖前面的结果时
  • 并行:当请求之间相互独立时
// 环境:浏览器 / Node.js 18+
// 场景:混合使用串行和并行

async function smartFetch() {
  // 第一步:获取用户信息(必须先执行)
  const user = await fetch(`/api/user/${userId}`);
  
  // 第二步:并行获取用户的多个资源(互不依赖)
  const [posts, friends, settings] = await Promise.all([
    fetch(`/api/posts?userId=${user.id}`),
    fetch(`/api/friends?userId=${user.id}`),
    fetch(`/api/settings?userId=${user.id}`)
  ]);
  
  return { user, posts, friends, settings };
}

场景 2:并发限制

在实际开发中,我们常常需要限制并发数量,避免一次性发起太多请求。

// 环境:浏览器 / Node.js 18+
// 场景:实现一个并发限制的请求调度器

class RequestScheduler {
  constructor(maxConcurrent = 3) {
    this.maxConcurrent = maxConcurrent;
    this.currentCount = 0;
    this.queue = [];
  }

  async add(promiseCreator) {
    // 如果达到并发上限,加入队列等待
    while (this.currentCount >= this.maxConcurrent) {
      await new Promise(resolve => this.queue.push(resolve));
    }

    this.currentCount++;
    
    try {
      return await promiseCreator();
    } finally {
      this.currentCount--;
      
      // 通知队列中的下一个请求
      const resolve = this.queue.shift();
      if (resolve) resolve();
    }
  }
}

// 使用示例
const scheduler = new RequestScheduler(3);

const urls = Array.from({ length: 10 }, (_, i) => `/api/item/${i}`);

async function fetchAll() {
  const promises = urls.map(url => 
    scheduler.add(() => fetch(url))
  );
  
  const results = await Promise.all(promises);
  console.log('All fetched:', results);
}

// 最多同时只有 3 个请求在执行
fetchAll();

场景 3:重试逻辑

处理不稳定的网络请求时,重试机制很有用:

// 环境:浏览器 / Node.js 18+
// 场景:带重试机制的请求函数

async function fetchWithRetry(url, options = {}) {
  const { maxRetries = 3, delay = 1000 } = options;
  
  for (let i = 0; i <= maxRetries; i++) {
    try {
      const response = await fetch(url);
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      
      return response;
    } catch (error) {
      // 如果是最后一次重试,抛出错误
      if (i === maxRetries) {
        throw error;
      }
      
      // 等待一段时间后重试
      console.log(`Retry ${i + 1}/${maxRetries} after ${delay}ms...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

// 使用
fetchWithRetry('/api/unstable-endpoint', { maxRetries: 5, delay: 2000 })
  .then(response => console.log('Success:', response))
  .catch(error => console.error('All retries failed:', error));

常见陷阱与最佳实践

陷阱 1:forEach 中的 async/await

// 环境:浏览器 / Node.js 18+
// 场景:forEach 不会等待 async 函数

// ❌ 错误做法:forEach 不会等待
async function processItems(items) {
  items.forEach(async (item) => {
    await processItem(item); // 不会按预期等待
  });
  console.log('All done?'); // 会立即执行
}

// ✅ 正确做法 1:使用 for...of
async function processItems(items) {
  for (const item of items) {
    await processItem(item); // 会等待
  }
  console.log('All done!'); // 所有处理完成后才执行
}

// ✅ 正确做法 2:使用 Promise.all(如果可以并行)
async function processItems(items) {
  await Promise.all(items.map(item => processItem(item)));
  console.log('All done!');
}

为什么 forEach 不工作?

因为 forEach 的回调函数返回值会被忽略,即使你返回了 Promise,forEach 也不会等待它。

陷阱 2:Promise 构造函数中的错误

// 环境:浏览器 / Node.js 18+
// 场景:Promise 构造函数中的错误处理

// ❌ 错误会被自动捕获
const promise1 = new Promise((resolve, reject) => {
  throw new Error('Error in executor');
});

promise1.catch(error => {
  console.log('Caught:', error.message); // 会捕获到
});

// ⚠️ 但异步错误不会
const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    throw new Error('Async error'); // 不会被 catch 捕获!
  }, 100);
});

promise2.catch(error => {
  console.log('Caught:', error.message); // 不会执行
});

// ✅ 正确做法:显式 reject
const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(new Error('Async error')); // 会被捕获
  }, 100);
});

陷阱 3:async 函数中的返回值

// 环境:浏览器 / Node.js 18+
// 场景:async 函数的返回值

async function test1() {
  return 'hello'; // 返回 Promise.resolve('hello')
}

async function test2() {
  return Promise.resolve('hello'); // 返回 Promise.resolve('hello')
}

async function test3() {
  throw new Error('error'); // 返回 Promise.reject(Error)
}

// 所有 async 函数都返回 Promise
test1().then(console.log); // 'hello'
test2().then(console.log); // 'hello'
test3().catch(console.error); // Error: error

手写实现:理解原理的最佳方式

手写简化版 Promise

为了深入理解 Promise 的原理,我尝试手写了一个简化版的实现:

// 环境:浏览器 / Node.js 18+
// 场景:手写简化版 Promise(不完全符合 Promise/A+ 规范)

class SimplePromise {
  constructor(executor) {
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onFulfilledCallbacks.forEach(fn => fn());
      }
    };

    const reject = (reason) => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    };

    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };

    const promise2 = new SimplePromise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value);
            resolve(x);
          } catch (error) {
            reject(error);
          }
        }, 0);
      }

      if (this.state === 'rejected') {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            resolve(x);
          } catch (error) {
            reject(error);
          }
        }, 0);
      }

      if (this.state === 'pending') {
        this.onFulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onFulfilled(this.value);
              resolve(x);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });

        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onRejected(this.reason);
              resolve(x);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });
      }
    });

    return promise2;
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }
}

// 测试
const p = new SimplePromise((resolve, reject) => {
  setTimeout(() => resolve('Success!'), 1000);
});

p.then(value => {
  console.log(value); // 'Success!'
  return value + ' Again';
}).then(value => {
  console.log(value); // 'Success! Again'
});

这个实现虽然简化了很多细节,但展示了 Promise 的核心机制:状态管理、回调队列、链式调用。

手写 Promise.all

// 环境:浏览器 / Node.js 18+
// 场景:手写 Promise.all

function promiseAll(promises) {
  return new Promise((resolve, reject) => {
    // 边界情况:空数组
    if (!Array.isArray(promises)) {
      return reject(new TypeError('Argument must be an array'));
    }

    if (promises.length === 0) {
      return resolve([]);
    }

    const results = [];
    let completedCount = 0;

    promises.forEach((promise, index) => {
      // 将值包装成 Promise(处理非 Promise 值)
      Promise.resolve(promise)
        .then(value => {
          results[index] = value;
          completedCount++;

          // 所有都完成时 resolve
          if (completedCount === promises.length) {
            resolve(results);
          }
        })
        .catch(error => {
          // 任何一个失败就 reject
          reject(error);
        });
    });
  });
}

// 测试
promiseAll([
  Promise.resolve(1),
  Promise.resolve(2),
  Promise.resolve(3)
]).then(results => {
  console.log(results); // [1, 2, 3]
});

设计思想与演进

从 Callback 到 Promise:控制权的反转

回调函数的问题在于"控制反转"(Inversion of Control):你把控制权交给了第三方代码,无法保证回调会如何被调用。

Promise 通过"反转回控制反转"解决了这个问题:

// Callback:你把控制权交出去
doSomethingAsync(data, (error, result) => {
  // 你不知道这个回调什么时候被调用、会被调用几次
});

// Promise:你保留控制权
const promise = doSomethingAsync(data);
promise.then(result => {
  // 你决定何时、如何处理结果
});

从 Promise 到 async/await:语法的革新

Promise 虽然解决了回调地狱,但链式调用仍然不够直观。async/await 让异步代码看起来像同步代码,降低了认知负担。

// Promise 链式调用
function loadData() {
  return fetchUser()
    .then(user => fetchPosts(user.id))
    .then(posts => fetchComments(posts[0].id))
    .then(comments => {
      return { comments };
    });
}

// async/await
async function loadData() {
  const user = await fetchUser();
  const posts = await fetchPosts(user.id);
  const comments = await fetchComments(posts[0].id);
  return { comments };
}

这种演进体现了一个设计哲学:让复杂的事情看起来简单

函数式编程的影响

Promise 的设计受到函数式编程的深刻影响:

  1. 不可变性:Promise 一旦确定状态就不会改变
  2. 可组合性:Promise 可以通过 .then() 链式组合
  3. 声明式编程:关注"做什么"而不是"怎么做"

这些思想让异步代码更容易推理、测试和维护。

延伸与思考

在 AI 辅助编程时代,理解 Promise 还重要吗?

这个问题我在上一篇 Event Loop 文章中也思考过。经过实践,我发现:

理解 Promise 让你能更好地使用 AI:

  1. 判断 AI 生成代码的正确性:如果你不理解 Promise,如何判断 AI 给出的异步方案是否合理?

  2. 提出更精准的问题

    • 含糊:❌ "这个 Promise 有问题"
    • 精准:✅ "为什么这个 Promise 链中的错误没有被 catch 捕获?"
  3. 性能优化的决策:AI 可能给出串行方案,但你需要判断是否应该改成并行。

一个实际例子:

AI 可能给你这样的代码:

// AI 生成的代码
async function loadAllData(ids) {
  const results = [];
  for (const id of ids) {
    const data = await fetchData(id); // 串行执行
    results.push(data);
  }
  return results;
}

如果你理解 Promise,就会知道可以优化成:

// 优化后的并行版本
async function loadAllData(ids) {
  return Promise.all(ids.map(id => fetchData(id)));
}

未来的异步方案

JavaScript 的异步方案还在继续演进:

  1. Top-level await:在模块顶层直接使用 await
  2. AbortController:可取消的 Promise
  3. Async Iterator:异步数据流的处理

这些新特性进一步简化了异步编程,但它们都建立在 Promise 的基础之上。

待探索的问题

在研究 Promise 的过程中,我产生了一些新的疑问:

  1. Promise 和 Observable 的关系是什么? RxJS 的 Observable 似乎更强大,为什么 Promise 仍然是主流?
  2. 在 React 中如何优雅地处理 Promise? useEffect 中不能直接用 async,该如何处理?
  3. Worker 线程中的 Promise 如何与主线程通信? 跨线程的异步应该如何设计?
  4. 如何实现真正可取消的 Promise? 标准的 Promise 无法取消,实际项目中如何解决?

小结

Promise 和 async/await 不仅仅是解决异步问题的工具,它们代表了一种编程范式的转变:从命令式到声明式,从回调地狱到优雅的线性代码。

通过这次学习,我对异步编程有了更深的理解:

  1. Promise 解决的不只是语法问题,更是控制权、错误处理、可组合性的问题
  2. async/await 的本质是语法糖,理解它如何转换成 Promise 帮助我们理解 Event Loop
  3. 选择串行还是并行,取决于业务逻辑而不是语法习惯
  4. 错误处理需要统筹考虑,而不是到处 try-catch

这篇文章是我的学习笔记,而非权威教程。如果你有不同的理解或补充,欢迎交流讨论。

最后留一个开放性问题:在你的实际开发中,遇到过哪些 Promise 相关的坑?你是如何解决的?

参考资料