Promise 和 async/await:从用途到执行机制

Promise 和 async/await:从用途到执行机制

前面复习同步异步时,我们已经知道:

text 复制代码
Promise.then 是微任务。
async/await 中 await 后面的代码也可以理解成微任务。

但是面试里只知道执行顺序还不够。还需要知道:

text 复制代码
Promise 是什么?
Promise 有什么用?
async/await 和 Promise 是什么关系?
为什么说 async/await 是 Promise 的语法糖?

1. 为什么需要 Promise

JS 里有很多异步场景:

  • 定时器
  • 网络请求
  • 文件读取
  • 用户事件
  • 数据库操作

早期处理异步,最常见的是回调函数。

js 复制代码
setTimeout(function () {
  console.log('异步完成');
}, 1000);

这种写法本身没问题。

但如果多个异步任务有先后依赖,就容易变成回调嵌套:

js 复制代码
getUser(function (user) {
  getOrders(user.id, function (orders) {
    getOrderDetail(orders[0].id, function (detail) {
      console.log(detail);
    });
  });
});

这种代码可读性差、维护困难,也就是常说的"回调地狱"。

所以 Promise 出现的主要目的就是:

text 复制代码
用链式调用的方式管理异步流程,解决回调嵌套问题。

2. Promise 是什么

Promise 是一个表示异步操作最终结果的对象。

它可以理解成一个"承诺":

text 复制代码
现在结果还没出来,但未来一定会变成成功或失败。

Promise 有三种状态:

  • pending:进行中
  • fulfilled:成功
  • rejected:失败

状态变化只能发生一次:

text 复制代码
pending -> fulfilled
pending -> rejected

一旦状态改变,就不会再变。

3. Promise 基础语法

js 复制代码
const promise = new Promise(function (resolve, reject) {
  const success = true;

  if (success) {
    resolve('成功结果');
  } else {
    reject('失败原因');
  }
});

promise
  .then(function (res) {
    console.log(res);
  })
  .catch(function (err) {
    console.log(err);
  })
  .finally(function () {
    console.log('结束');
  });

这里要注意:

text 复制代码
new Promise 里的执行器函数是同步执行的。
then / catch / finally 里的回调是异步执行的,属于微任务。

比如:

js 复制代码
console.log('1');

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

console.log('4');

输出:

text 复制代码
1
2
4
3

4. resolve 和 reject 是干什么的

resolve 用来把 Promise 状态改成成功。

js 复制代码
resolve('data');

成功后的结果会传给 then

js 复制代码
promise.then((data) => {
  console.log(data);
});

reject 用来把 Promise 状态改成失败。

js 复制代码
reject('error');

失败原因会传给 catch

js 复制代码
promise.catch((err) => {
  console.log(err);
});

5. Promise 链式调用

Promise 支持链式调用。

js 复制代码
Promise.resolve(1)
  .then((res) => {
    console.log(res); // 1
    return res + 1;
  })
  .then((res) => {
    console.log(res); // 2
    return res + 1;
  })
  .then((res) => {
    console.log(res); // 3
  });

为什么可以一直 .then()

因为:

text 复制代码
then 方法会返回一个新的 Promise。

上一个 then 的返回值,会成为下一个 then 的参数。

如果 then 里返回的是普通值:

js 复制代码
return 123;

下一个 then 会拿到 123

如果 then 里返回的是 Promise:

js 复制代码
return Promise.resolve(123);

下一个 then 会等这个 Promise 完成后,再拿到结果。

6. Promise 常用方法

Promise.resolve

快速创建一个成功状态的 Promise。

js 复制代码
Promise.resolve('ok').then((res) => {
  console.log(res); // ok
});

Promise.reject

快速创建一个失败状态的 Promise。

js 复制代码
Promise.reject('error').catch((err) => {
  console.log(err); // error
});

Promise.all

等待所有 Promise 都成功。

js 复制代码
Promise.all([requestA(), requestB(), requestC()])
  .then((res) => {
    console.log(res);
  });

特点:

text 复制代码
全部成功,才会成功。
只要有一个失败,整体就失败。

适合多个请求都完成后再继续。

Promise.race

谁先完成,就用谁的结果。

js 复制代码
Promise.race([requestA(), requestB()])
  .then((res) => {
    console.log(res);
  });

适合做请求超时控制。

Promise.allSettled

等待所有 Promise 都结束,不管成功还是失败。

js 复制代码
Promise.allSettled([requestA(), requestB()])
  .then((res) => {
    console.log(res);
  });

适合需要收集所有结果的场景。

7. Promise 解决了什么问题

Promise 主要解决三个问题:

text 复制代码
1. 解决回调地狱。
2. 统一异步成功和失败的处理方式。
3. 让多个异步任务可以链式组合。

回调写法:

js 复制代码
getUser(function (user) {
  getOrders(user.id, function (orders) {
    console.log(orders);
  });
});

Promise 写法:

js 复制代码
getUser()
  .then((user) => getOrders(user.id))
  .then((orders) => {
    console.log(orders);
  })
  .catch((err) => {
    console.log(err);
  });

8. async/await 是什么

async/await 是基于 Promise 的异步语法。

它可以让异步代码写起来更像同步代码。

Promise 写法:

js 复制代码
function getData() {
  return fetch('/api/user')
    .then((res) => res.json())
    .then((data) => {
      console.log(data);
    });
}

async/await 写法:

js 复制代码
async function getData() {
  const res = await fetch('/api/user');
  const data = await res.json();
  console.log(data);
}

这两种写法本质上都是 Promise。

区别是:

text 复制代码
async/await 写法更像同步代码,可读性更好。

9. async 函数有什么特点

只要函数前面加了 async,这个函数就一定返回 Promise。

js 复制代码
async function fn() {
  return 123;
}

console.log(fn());

输出的是一个 Promise:

text 复制代码
Promise fulfilled 123

等价于:

js 复制代码
function fn() {
  return Promise.resolve(123);
}

所以:

js 复制代码
async function fn() {
  return 123;
}

fn().then((res) => {
  console.log(res); // 123
});

10. await 是干什么的

await 用来等待一个 Promise 的结果。

js 复制代码
async function fn() {
  const res = await Promise.resolve('hello');
  console.log(res);
}

输出:

text 复制代码
hello

如果 await 后面不是 Promise,会被包装成 Promise。

js 复制代码
async function fn() {
  const res = await 123;
  console.log(res);
}

可以理解成:

js 复制代码
await Promise.resolve(123);

11. await 前后执行顺序

js 复制代码
async function fn() {
  console.log('1');
  await 100;
  console.log('2');
}

fn();
console.log('3');

输出:

text 复制代码
1
3
2

原因:

text 复制代码
await 前面的代码同步执行。
await 后面的代码会被放到微任务中执行。

可以粗略理解成:

js 复制代码
async function fn() {
  console.log('1');

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

所以 console.log('3') 会先执行,然后再执行 await 后面的代码。

12. 为什么说 async/await 是语法糖

语法糖的意思是:

text 复制代码
语法更好写、更好读,但底层能力不是全新的。

async/await 底层仍然是基于 Promise。

比如:

js 复制代码
async function getUser() {
  const user = await requestUser();
  const orders = await requestOrders(user.id);
  return orders;
}

本质上类似于:

js 复制代码
function getUser() {
  return requestUser()
    .then((user) => {
      return requestOrders(user.id);
    })
    .then((orders) => {
      return orders;
    });
}

所以可以说:

text 复制代码
async/await 是 Promise 的语法糖。

它没有改变 JS 异步机制,只是让 Promise 链式调用写起来更像同步流程。

13. async/await 大概是怎么实现的

更底层一点看,async/await 可以理解为:

text 复制代码
Generator 函数 + 自动执行器 + Promise

先看 Generator:

js 复制代码
function* gen() {
  const a = yield Promise.resolve(1);
  const b = yield Promise.resolve(a + 1);
  return b;
}

Generator 的特点是可以暂停和继续执行。

js 复制代码
const iterator = gen();
iterator.next();  // 执行到第一个 yield 暂停
iterator.next(1); // 把 1 传回去,继续执行到第二个 yield
iterator.next(2); // 继续执行到 return

async/await 的思想就是:

text 复制代码
遇到 await,先暂停函数。
等 await 后面的 Promise 完成后,再继续执行后面的代码。

可以用一个简化版自动执行器理解:

js 复制代码
function run(generatorFn) {
  const iterator = generatorFn();

  function next(data) {
    const result = iterator.next(data);

    if (result.done) {
      return Promise.resolve(result.value);
    }

    return Promise.resolve(result.value).then((res) => {
      return next(res);
    });
  }

  return next();
}

使用:

js 复制代码
function* getData() {
  const user = yield Promise.resolve({ id: 1 });
  const orders = yield Promise.resolve(['order1', 'order2']);
  return { user, orders };
}

run(getData).then((res) => {
  console.log(res);
});

这就是 async/await 的核心思想:

text 复制代码
用 Promise 管理异步结果。
用暂停和恢复的机制,让异步代码看起来像同步代码。

面试不一定要手写完整实现,但要知道:

text 复制代码
async/await 本质上是 Promise 的语法糖,早期可以用 Generator + Promise 自动执行器模拟。

14. 错误处理

Promise 用 catch 处理错误:

js 复制代码
requestUser()
  .then((user) => {
    return requestOrders(user.id);
  })
  .catch((err) => {
    console.log(err);
  });

async/await 用 try...catch 处理错误:

js 复制代码
async function getData() {
  try {
    const user = await requestUser();
    const orders = await requestOrders(user.id);
    console.log(orders);
  } catch (err) {
    console.log(err);
  }
}

这也是 async/await 更像同步代码的地方。

15. Promise 和 async/await 的关系

可以这样总结:

text 复制代码
Promise 是 JS 处理异步结果的一种对象和规范。
async/await 是基于 Promise 的更舒服的写法。

它们不是两个完全独立的东西。

async 函数返回 Promise:

js 复制代码
async function fn() {
  return 'hello';
}

等价于:

js 复制代码
function fn() {
  return Promise.resolve('hello');
}

await 等待 Promise:

js 复制代码
const data = await request();

类似于:

js 复制代码
request().then((data) => {
  // 后续代码
});

16. 一道综合执行顺序题

js 复制代码
console.log('1');

async function async1() {
  console.log('2');
  await async2();
  console.log('3');
}

async function async2() {
  console.log('4');
}

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

new Promise((resolve) => {
  console.log('6');
  resolve();
}).then(() => {
  console.log('7');
});

async1();

console.log('8');

分析:

同步任务:

text 复制代码
1
6
2
4
8

微任务:

text 复制代码
7
3

宏任务:

text 复制代码
5

最终输出:

text 复制代码
1
6
2
4
8
7
3
5

注意:

text 复制代码
async2 虽然是 async 函数,但它里面 await 前面的代码仍然是同步执行。
await 后面的 console.log('3') 会进入微任务。

17. 面试怎么答

如果面试官问:

text 复制代码
说一下 Promise。

可以这样答:

text 复制代码
Promise 是 JS 处理异步的一种方案,它表示一个异步操作最终成功或失败的结果。Promise 有 pending、fulfilled、rejected 三种状态,状态一旦从 pending 变成 fulfilled 或 rejected 就不能再改变。Promise 通过 then、catch、finally 处理成功、失败和结束逻辑,并且 then 会返回新的 Promise,所以可以链式调用,从而解决回调地狱问题。

如果面试官问:

text 复制代码
说一下 async/await。

可以这样答:

text 复制代码
async/await 是 Promise 的语法糖,用同步写法来表达异步流程。async 函数一定返回 Promise,await 用来等待 Promise 的结果。await 前面的代码同步执行,await 后面的代码会作为微任务执行。它本质上没有改变 JS 的异步机制,只是让 Promise 链式调用更清晰,早期可以用 Generator + Promise 自动执行器来模拟。

18. 最后总结

Promise 解决的是:

text 复制代码
异步结果如何表示和组合。

async/await 解决的是:

text 复制代码
异步代码如何写得更像同步代码。

两者关系:

text 复制代码
Promise 是基础。
async/await 是基于 Promise 的语法糖。

执行顺序:

text 复制代码
new Promise 执行器同步执行。
then / catch / finally 是微任务。
async 函数返回 Promise。
await 前面同步执行。
await 后面进入微任务。
相关推荐
yyt3630458411 小时前
KLineChartQuant:GLSL 的精度问题及 RTC 解法
前端·vue.js·react.js·交互·图形渲染·webgl·数据可视化
HarmonLTS1 小时前
智盾 WAF v8.2 Ultra|下一代 Web 应用防火墙
前端
এ慕ོ冬℘゜1 小时前
前端实战:基于jQuery递归实现通用树形组织菜单(可直接复用)
前端·javascript·jquery
计算机魔术师2 小时前
英伟达据报洽谈向 Mira Murati 的 Thinking Machines Lab 投资 25 亿美元
前端
变与不变8062 小时前
js函数与封装详细解答
前端·javascript·vue.js
南雨北斗2 小时前
Vue 3 项目中的拦截器的作用和写法(动态设置响应头适配表单文件上传)
前端
涛涛ing2 小时前
125秒到10秒:TypeScript 7.0用Go重写编译器,前端圈等了14年
前端
爱勇宝3 小时前
初创公司的“自己人”,到底能当多久?
前端·后端·程序员
晴天163 小时前
前端模块化规范全景解析:CommonJS、AMD、ESM、UMD
前端