核心概念 JavaScript 是单线程语言,但通过异步编程实现非阻塞操作。主要包括:

  • Promise - 异步操作的标准化解决方案
  • async/await - Promise 的语法糖
  • 事件循环 - JavaScript 的执行机制

一、Promise

1.1 为什么需要 Promise?

回调地狱问题 异步任务嵌套多层会导致代码难以维护:

// ❌ 回调地狱
getData(function(a) {
  getMoreData(a, function(b) {
    getMoreData(b, function(c) {
      getMoreData(c, function(d) {
        // 无限嵌套...
      });
    });
  });
});

ES6 引入 Promise 对象,提供优雅的异步编程解决方案。

1.2 Promise 的三种状态

状态说明

  • pending(等待):初始状态,既没有成功也没有失败
  • fulfilled(已成功):操作成功完成
  • rejected(已失败):操作失败
stateDiagram-v2
    [*] --> pending: 创建 Promise
    pending --> fulfilled: resolve()
    pending --> rejected: reject()
    fulfilled --> [*]
    rejected --> [*]

状态特点

  • 状态不可逆,一旦改变就不会再变
  • resolvereject 只执行一个

1.3 基本用法

// 创建 Promise
const promise = new Promise((resolve, reject) => {
  // 异步操作
  const success = true;
 
  if (success) {
    resolve("成功的数据");  // 将状态改为 fulfilled
  } else {
    reject("失败的原因");   // 将状态改为 rejected
  }
});
 
// 使用 Promise
promise
  .then((data) => {
    console.log(data);  // "成功的数据"
  })
  .catch((error) => {
    console.error(error);
  })
  .finally(() => {
    console.log("无论成功失败都会执行");
  });

1.4 Promise 静态方法

方法说明返回值
Promise.resolve()创建一个已解决的 Promiseresolved Promise
Promise.reject()创建一个已拒绝的 Promiserejected Promise
Promise.all()全部成功才成功,有一个失败就失败Promise
Promise.allSettled()等待全部完成(不管成功失败)Promise
Promise.race()返回最先完成的结果Promise
Promise.any()返回第一个成功的结果Promise

Promise.all() - 全部成功

const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);
 
Promise.all([p1, p2, p3]).then((values) => {
  console.log(values);  // [1, 2, 3]
});
 
// 有一个失败就返回失败
const p4 = Promise.reject("失败");
Promise.all([p1, p2, p4]).catch((error) => {
  console.log(error);  // "失败"
});

Promise.race() - 赛跑

const p1 = new Promise((resolve) => setTimeout(() => resolve("A"), 100));
const p2 = new Promise((resolve) => setTimeout(() => resolve("B"), 200));
 
Promise.race([p1, p2]).then((value) => {
  console.log(value);  // "A"(最快完成的)
});

Promise.allSettled() - 全部完成

const p1 = Promise.resolve(1);
const p2 = Promise.reject("失败");
const p3 = Promise.resolve(3);
 
Promise.allSettled([p1, p2, p3]).then((results) => {
  console.log(results);
  // [
  //   { status: 'fulfilled', value: 1 },
  //   { status: 'rejected', reason: '失败' },
  //   { status: 'fulfilled', value: 3 }
  // ]
});

1.5 Promise 链式调用

fetchUser()
  .then((user) => {
    console.log("获取用户:", user);
    return fetchPosts(user.id);  // 返回新的 Promise
  })
  .then((posts) => {
    console.log("获取文章:", posts);
    return fetchComments(posts[0].id);
  })
  .then((comments) => {
    console.log("获取评论:", comments);
  })
  .catch((error) => {
    console.error("发生错误:", error);
  });

链式调用原理

  • then() 方法返回一个新的 Promise
  • 可以通过 return 值传递给下一个 then()

1.6 Promise 常见问题

错误处理

// ✅ 推荐:使用 catch
promise
  .then((data) => { })
  .catch((error) => {
    console.error(error);
  });
 
// ⚠️ 注意:then 的第二个参数也可以捕获错误
promise.then(
  (data) => { },
  (error) => { }  // 但无法捕获前面 then 中的错误
);
 
// ✅ 推荐:try/catch (async/await)
async function handle() {
  try {
    const data = await promise;
  } catch (error) {
    console.error(error);
  }
}

Promise 穿透

Promise.resolve(1)
  .then(null)  // 空的 then 会透传值
  .then((value) => console.log(value));  // 1

二、async/await

async/await 是 Promise 的语法糖

  • async 函数:返回 Promise
  • await 表达式:等待 Promise 完成

2.1 基本语法

// async 函数声明
async function fetchData() {
  return "数据";  // 自动包装成 Promise.resolve("数据")
}
 
// 等同于
function fetchData() {
  return Promise.resolve("数据");
}

2.2 await 使用

async function getData() {
  // 等待 Promise 完成
  const data = await fetchUser();
 
  // 等待另一个 Promise
  const posts = await fetchPosts(data.id);
 
  return posts;
}
 
// 使用
getData().then((posts) => {
  console.log(posts);
});

await 只能在 async 函数内使用

// ❌ 错误
function test() {
  await promise;  // SyntaxError
}
 
// ✅ 正确
async function test() {
  await promise;
}

2.3 错误处理

async function fetchData() {
  try {
    const data = await fetchUser();
    console.log(data.message);
  } catch (error) {
    console.error('Error:', error.message);
  } finally {
    console.log('Finally block executed');
  }
}

2.4 并发处理

// ❌ 串行执行(慢)
async function serial() {
  const a = await fetchA();  // 等待 1s
  const b = await fetchB();  // 等待 1s
  const c = await fetchC();  // 等待 1s
  // 总共 3s
}
 
// ✅ 并行执行(快)
async function parallel() {
  const [a, b, c] = await Promise.all([
    fetchA(),  // 1s
    fetchB(),  // 1s
    fetchC()   // 1s
  ]);
  // 总共 1s
}
 
// ✅ 互不依赖的并发
async function independent() {
  const p1 = fetchA();
  const p2 = fetchB();
 
  const a = await p1;
  const b = await p2;
}

2.5 async/await vs Promise

特性async/awaitPromise
可读性高(同步写法)低(链式调用)
错误处理try/catch.catch()
中间值处理方便需要在外层作用域
调试同步调试断点跳跃

推荐使用 async/await 代码更简洁,错误处理更直观

三、事件循环(Event Loop)

3.1 JavaScript 是单线程的

为什么需要事件循环? JavaScript 是单线程语言,同一时间只能做一件事。为了处理异步操作,使用事件循环机制。

3.2 任务分类

同步代码 vs 异步代码

// 同步代码:立即执行
console.log(1);
 
// 异步代码:稍后执行
setTimeout(() => console.log(2), 0);

宏任务 vs 微任务

类型说明示例
宏任务宿主环境(浏览器)发起的任务setTimeout、setInterval、UI 渲染、DOM 事件
微任务JavaScript 引擎发起的任务Promise.then、MutationObserver、queueMicrotask

3.3 事件循环执行顺序

graph TD
    A[开始] --> B[执行同步代码]
    B --> C[同步代码执行完毕?]
    C -->|否| B
    C -->|是| D[执行微任务队列]
    D --> E[微任务队列为空?]
    E -->|否| D
    E -->|是| F[从宏任务队列取一个]
    F --> G[执行宏任务]
    G --> B
console.log("1. 开始");
 
setTimeout(() => {
  console.log("2. 宏任务:setTimeout");
}, 0);
 
Promise.resolve().then(() => {
  console.log("3. 微任务:Promise");
});
 
console.log("4. 结束");
 
// 输出顺序:
// 1. 开始(同步)
// 4. 结束(同步)
// 3. 微任务:Promise
// 2. 宏任务:setTimeout

3.4 执行优先级

process.nextTick(Node.js) > Promise.then > setTimeout > setImmediate

注意事项

  • new Promise() 中的代码是同步执行
  • then/catch 回调是异步
  • requestAnimationFrame 和定时器顺序不一定

3.5 经典面试题

setTimeout(() => console.log(1), 0);
 
new Promise((resolve) => {
  console.log(2);  // 同步代码
  resolve();
  console.log(3);  // 同步代码
}).then(() => {
  console.log(4);  // 微任务
});
 
console.log(5);  // 同步代码
 
// 输出顺序:2, 3, 5, 4, 1

解析:

  1. 同步代码:console.log(2)console.log(3)console.log(5)
  2. 微任务队列:then 回调
  3. 宏任务队列:setTimeout 回调
  4. 清空微任务:console.log(4)
  5. 执行宏任务:console.log(1)

四、最佳实践

异步编程建议

  1. 优先使用 async/await 而非 Promise 链式调用
  2. 并发请求使用 Promise.all()
  3. 总是使用 try/catch 捕获错误
  4. 避免在循环中 await,使用 Promise.all()
  5. 理解事件循环,避免阻塞主线程
// ✅ 推荐写法
async function fetchUsers() {
  try {
    const [users, posts] = await Promise.all([
      fetch("/api/users"),
      fetch("/api/posts")
    ]);
    return { users, posts };
  } catch (error) {
    console.error("请求失败:", error);
    throw error;
  }
}

相关链接