目录
- [一、先搞懂:同步 / 异步](#一、先搞懂:同步 / 异步)
- 1). 同步. 同步)
- 2). 异步. 异步)
- [二、Promise 到底是什么?](#二、Promise 到底是什么?)
- 基础用法
- [什么叫 JS 内置构造函数?](#什么叫 JS 内置构造函数?)
- [1. 先懂什么是构造函数](#1. 先懂什么是构造函数)
- [2. 什么叫 内置?](#2. 什么叫 内置?)
- [3. 所以合起来:](#3. 所以合起来:)
- [三、Promise 常用方法(面试必背 + 工作高频)](#三、Promise 常用方法(面试必背 + 工作高频))
- [1. 实例方法(对象调用)](#1. 实例方法(对象调用))
- [2. 静态方法(类直接调用)](#2. 静态方法(类直接调用))
- Promise.allSettled():等待全部结束,成功失败都返回结果
- [1. 实例方法(通过对象调用)](#1. 实例方法(通过对象调用))
- [2. 静态方法(直接用类名调用)](#2. 静态方法(直接用类名调用))
- 最简单记忆法
- [场景 A:并发请求(最常用)](#场景 A:并发请求(最常用))
- [场景 B:超时控制](#场景 B:超时控制)
- [场景 C:批量上传图片](#场景 C:批量上传图片)
- [场景 D:备用接口](#场景 D:备用接口)
- [四、async/await 语法糖(Promise 的升级版)](#四、async/await 语法糖(Promise 的升级版))
- 核心结论
- [4 条使用规则](#4 条使用规则)
- 对比写法(最直观)
- [1)Promise 链式写法](#1)Promise 链式写法)
- [2)async/await 写法(更优雅、更易读)](#2)async/await 写法(更优雅、更易读))
- 执行顺序
- [五、Promise vs async/await 区别(面试标准答案)](#五、Promise vs async/await 区别(面试标准答案))
- [1. 什么是宏任务(普通任务)](#1. 什么是宏任务(普通任务))
- [2. 什么是微任务(VIP 插队任务)](#2. 什么是微任务(VIP 插队任务))
- [3. 执行顺序铁律(必须背)](#3. 执行顺序铁律(必须背))
- [六、哪些东西会返回 Promise?(工作常用)](#六、哪些东西会返回 Promise?(工作常用))
- 七、最容易混淆的点(一定要记住)
- 总结
- 手写题(高频)
- [1. 手写 Promise.all](#1. 手写 Promise.all)
- [2. 手写 Promise.race](#2. 手写 Promise.race)
- [3. 手写 Promise 重复请求 / 重试(常考)](#3. 手写 Promise 重复请求 / 重试(常考))
- 面试题
- [1. Promise 状态有哪几种?](#1. Promise 状态有哪几种?)
- [2. then 为什么能链式调用?](#2. then 为什么能链式调用?)
- [3. Promise.all 和 allSettled 区别?](#3. Promise.all 和 allSettled 区别?)
- [4. race 和 any 区别?](#4. race 和 any 区别?)
- [5. async 函数返回什么?](#5. async 函数返回什么?)
- [6. await 后面跟普通函数会怎样?](#6. await 后面跟普通函数会怎样?)
- [7. Promise 中同步代码和微任务执行顺序?](#7. Promise 中同步代码和微任务执行顺序?)
- [8. 手写 Promise.all(面试高频)](#8. 手写 Promise.all(面试高频))
- [9. 如何中断 Promise?](#9. 如何中断 Promise?)
- [10. 什么是宏任务 / 微任务?](#10. 什么是宏任务 / 微任务?)
- 宏任务(macrotask)
- 微任务(microtask)
- 执行顺序铁律
一、先搞懂:同步 / 异步
1). 同步
代码从上到下按顺序执行,必须等上一行执行完毕,下一行才会运行。
- 会阻塞后续代码
2). 异步
代码不会等待,先继续执行后面的同步代码,等异步任务完成后再回头执行回调。
- 不会阻塞代码
- 常见异步任务:
- 定时器:
setTimeout、setInterval - 网络请求:AJAX /fetch/axios
- 文件操作(Node.js)
- Promise、async/await
- 定时器:
二、Promise 到底是什么?
- JS 内置构造函数,专门处理异步操作
- 核心作用:解决回调地狱(多层嵌套回调)
- 本质:存放异步未来结果的容器 (本身不是异步,
.then / await是异步)
基础用法
javascript
// 1. 创建 Promise
const p = new Promise((resolve, reject) => {
// 异步任务:定时器、ajax、fetch...
setTimeout(() => {
resolve("成功数据"); // 成功 → 触发 then
// reject("失败原因"); // 失败 → 触发 catch
}, 1000);
});
// 2. 使用 Promise
p.then(res => {
console.log(res); // 成功
}).catch(err => {
console.log(err); // 失败
}).finally(() => {
console.log("无论成功失败都执行"); // 清理工作
});
什么叫 JS 内置构造函数?
一句话:JS 语言自带的、用来造东西的 "工厂函数",就叫内置构造函数。
1. 先懂什么是构造函数
构造函数 = 用来创建对象的函数特点:
- 名字一般首字母大写
- 必须用 new 来调用
- 调用后会返回一个新对象
比如:
new Promise() new Array() new Object() new Date()这些都是构造函数。
2. 什么叫 内置?
内置 = JS 引擎自带,不用你引入,不用你定义,直接就能用。
不是你写的,不是库带的,是浏览器 / Node 天生就有的。
3. 所以合起来:
JS 内置构造函数 = JS 自带的、用 new 来创建对象的函数
三、Promise 常用方法(面试必背 + 工作高频)
1. 实例方法(对象调用)
.then():异步成功时执行.catch():异步失败时执行.finally():无论成功 / 失败都执行(关闭加载、清理资源)
2. 静态方法(类直接调用)
Promise.resolve():快速创建成功的 PromisePromise.reject():快速创建失败的 PromisePromise.all():等待全部成功,一个失败 → 整体失败Promise.race():谁先完成就返回谁(不管成功失败)
Promise.allSettled():等待全部结束,成功失败都返回结果
Promise.any():只要一个成功就返回,全部失败才失败
1. 实例方法(通过对象调用)
先 new 一个 Promise 对象,再调用:
javascriptconst p = new Promise(...) p.then(...) p.catch(...) p.finally(...)
then/catch/finally- 必须先有 Promise 对象才能用
- 叫实例方法
2. 静态方法(直接用类名调用)
不需要 new,直接用 Promise.xxx ()
javascriptPromise.all(...) Promise.race(...) Promise.resolve(...) Promise.reject(...) Promise.allSettled(...) Promise.any(...)
- 直接用
Promise这个名字点出来- 不需要创建对象
- 这就叫静态方法
最简单记忆法
- 对象。方法 → 实例方法
- 类名。方法 → 静态方法
| 方法名 | 别名(外号) | 成功条件 | 失败条件 | 返回结果 | 适用场景 |
|---|---|---|---|---|---|
| Promise.resolve | 成功工厂 | 立即成功 | - | 成功的 Promise | 快速包装值、跳过异步流程 |
| Promise.reject | 失败工厂 | - | 立即失败 | 失败的 Promise | 主动抛出错误、统一错误处理 |
| Promise.all | 全部都要 | 全部成功 | 一个失败 | 结果数组(顺序对应) | 并行加载多个资源,做批量初始化 |
| Promise.race | 谁抢得快 | 第一个完成(成功) | 第一个完成(失败) | 第一个完成的值 | 超时控制、请求竞态、抢最快接口 |
| Promise.allSettled | 全部过完 | 无论成功失败 | - | 结果数组(含状态) | 不关心成败,只需要全部处理完 |
| Promise.any | 只要一个赢 | 一个成功 | 全部失败 | 第一个成功的值 | 失败也没关系,只要有一个能用就好 |
场景 A:并发请求(最常用)
需求:一次性拿用户信息、订单、购物车数据,要等全部拿到才能渲染页面。
javascript
// 只要有一个挂了,直接进 catch
Promise.all([fetchUserInfo(), fetchOrders(), fetchCart()])
.then(([user, orders, cart]) => { ... })
- 策略:同时发三个请求,等它们全部返回成功,再渲染页面。
- 优势:总耗时 = 最慢的那个接口时间(不是时间之和),性能最快。
场景 B:超时控制
需求:接口如果 3 秒没返回,就报错。
javascript
// race 谁先赢算谁的
Promise.race([
fetchApi(),
new Promise((_, reject) => setTimeout(() => reject('超时'), 3000))
])
- 场景:支付接口如果 3 秒没返回,就自动超时报错,不要一直等。
- 策略 :把接口请求和定时器比赛放在
race里,谁先跑完(定时器或接口),谁就赢。
场景 C:批量上传图片
需求:上传 10 张图,有的图可能失败,有的成功,最后要把 10 张的状态全部记录下来。
javascript
// 不怕失败,全部跑完再汇报
Promise.allSettled([uploadImg1(), uploadImg2()])
.then(results => {
// results 里包含每个图片的 { status, reason/value }
})
- 场景:发朋友圈要上传 10 张图。
- 策略 :
- 不能因为 1 张图传失败了,就把其他 9 张删掉(不使用 all)。
- 也不需要最快的(不需要 race)。
- 只需要全部上传完,记录下哪些成功了,哪些失败了,最后给用户一个汇总提示。
场景 D:备用接口
需求:A 接口挂了,立马用 B 接口,只要有一个能用就行。
javascript
// 只要有一个成功就返回
Promise.any([fetchApiV1(), fetchApiV2()])
.then(res => { console.log('使用了可用的那个接口', res) })
- 场景:调用主接口 A 失败了,自动切接口 B。只要有一个能用就继续。
- 区别 :比
race更智能。race第一个失败就停了,any是只要有一个成功就停,全部失败才报错。
resolve /reject ------ 造 Promise 的工具
它们的作用只有一个:快速创建一个已经成功 / 失败的 Promise 对象。
什么时候用?
- 你想让一个函数立即返回一个结果
- 你想把一个普通值(如字符串、数字)变成 Promise
- 做缓存、做中断、做快速返回
all /race/allSettled /any ------ 协调多个 Promise 的指挥官
这些函数处理的是一组 Promise 数组,用来控制多个异步任务的执行顺序、结果收集等。
四、async/await 语法糖(Promise 的升级版)
核心结论
async/await 是 Promise 的语法糖,没有替代 Promise,只是让异步代码写起来像同步代码。
4 条使用规则
async必须写在函数前面await只能在async函数内部使用await后面必须跟 Promise 对象- 错误处理必须用
try/catch
对比写法(最直观)
1)Promise 链式写法
javascript
function getData() {
fetch("/api")
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.log(err));
}
2)async/await 写法(更优雅、更易读)
javascript
async function getData() {
try {
const res = await fetch("/api"); // 等待 Promise 完成
const data = await res.json();
console.log(data);
} catch (err) {
console.log(err); // 捕获错误
}
}
await 后面 = 整段 "被暂停的代码" 只要在 await 这一行的下面 ,都属于:await 等待完成后,才会执行的代码
javascriptasync function foo() { console.log('1'); // 立即执行(同步) // ↓↓↓ 遇到 await,函数在这里暂停 ↓↓↓ await new Promise(resolve => { setTimeout(() => { resolve(); }, 2000); }); // ↑↑↑ 等待 Promise 成功 ↑↑↑ // ====================================== // 👇👇👇 【这一整块】都是 await 后面的代码 👇👇👇 console.log('2'); console.log('别的代码'); function test() {} // 👆👆👆 【全部都要等 await 结束】 // ====================================== } foo(); console.log('3');执行顺序
- 打印
1- 遇到
await→ 暂停 foo 函数- 跳出函数 → 打印
3- 等 2 秒 → Promise 成功
- 恢复执行 foo 函数
- 打印
2
await = 暂停 + 等待 + 恢复
- 遇到 await 函数立刻暂停,下面所有代码都冻结
- 等待 等后面的 Promise 变成
成功(resolve)- 恢复执行 把 await 下面所有代码 当作微任务继续执行
五、Promise vs async/await 区别(面试标准答案)
| 对比项 | Promise | async/await |
|---|---|---|
| 本质 | 异步处理对象、解决回调地狱 | Promise 的语法糖 |
| 写法 | .then() 链式调用 |
同步风格代码 |
| 可读性 | 多层链式相对繁琐 | 更清晰、接近同步代码 |
| 错误处理 | .catch() |
try/catch |
| 依赖关系 | 独立存在 | 必须依赖 Promise |
| 执行顺序 | 基于微任务异步 | 同步写法,底层还是异步 |
**一句话总结:**Promise 是基础,async/await 是让 Promise 更好用、更易读的语法糖。
1. 什么是宏任务(普通任务)
可以理解成:排队的普通任务
常见宏任务:
setTimeout、setInterval- 网络请求(ajax/fetch/axios)
- I/O、文件操作
- DOM 事件(click、scroll)
- UI 渲染
特点:
- 每次事件循环只执行一个
- 执行完一个,就去检查微任务
2. 什么是微任务(VIP 插队任务)
可以理解成:优先执行的插队任务
常见微任务:
- Promise.then / catch / finally
- async/await 后面的代码
queueMicrotaskMutationObserver特点:
- 会一次性全部清空
- 优先级比宏任务高得多
3. 执行顺序铁律(必须背)
- 先执行所有同步代码
- 执行当前所有微任务(一口气清空)
- 再执行一个宏任务
- 再清空微任务
- 再执行下一个宏任务...... 循环
六、哪些东西会返回 Promise?(工作常用)
- 手动
return new Promise(...) - async 函数(自动包装成 Promise)
- 常用库 / API:
fetch()axios.get()- Node.js:
fs.promises、mysql2/promise
- Promise 静态方法:
resolve/reject/all/race等
七、最容易混淆的点(一定要记住)
- Promise 本身不是异步,它只是装异步结果的容器
.then()和await一定是异步(微任务)- async/await 不能脱离 Promise 单独使用
- 异步代码不会阻塞同步代码执行
async 函数 = 一定返回 Promise = 一定是异步不管里面有没有 await,都异步。
**await = 只是在函数内部 "等待并取出结果 "**不写 await,函数照样异步,只是不等待。
异步与否,看是不是 Promise,不看有没有 await
- 要在函数内部拿结果 → 用
await- 只返回 Promise 给外面 → 不用
await- 只要是
async 函数,永远异步,跟写不写 await 无关
总结
- 同步阻塞、异步不阻塞,Promise 专门解决异步回调地狱
- Promise 方法:then/catch/finally(实例) + all/race/allSettled/any(静态)
- async/await = Promise 语法糖,同步写法,异步效果,错误用 try/catch
- 所有 async/await 底层都是 Promise,二者相辅相成
await 后面是 Promise → 等它 resolve 后,再把后面代码放进微任务 await 后面不是 Promise → 直接把后面代码放进微任务
而且前面还有一个共同前提:
await 紧跟的那一行代码,永远同步立即执行
手写题(高频)
1. 手写 Promise.all
javascript
function all(promises) {
let results = []
let count = 0
return new Promise((resolve, reject) => {
promises.forEach((p, i) => {
Promise.resolve(p).then(res => {
results[i] = res
count++
if (count === promises.length) resolve(results)
}).catch(reject)
})
})
}
2. 手写 Promise.race
javascript
function race(promises) {
return new Promise((resolve, reject) => {
promises.forEach(p => {
Promise.resolve(p).then(resolve).catch(reject)
})
})
}
3. 手写 Promise 重复请求 / 重试(常考)
javascript
function retry(fn, times) {
return fn().catch(err => {
if (times > 0) return retry(fn, times - 1)
throw err
})
}
面试题
1. Promise 状态有哪几种?
三种状态:
pending:进行中(初始状态)fulfilled:已成功rejected:已失败
特点:
- 状态一旦从 pending 变为 fulfilled /rejected,就不可逆
- 状态改变后,结果会被永久保存
2. then 为什么能链式调用?
因为 .then() 会返回一个新的 Promise ,所以可以继续 .then().then() 链式调用。
返回规则:
- return 普通值 → 包装成
Promise.resolve(值) - return Promise → 沿用这个 Promise
- 抛错 → 变成
Promise.reject(错误)
3. Promise.all 和 allSettled 区别?
-
Promise.all
- 必须全部成功才成功
- 有一个失败 → 整体立即失败
- 适合:必须全部成功才能继续的场景
-
Promise.allSettled
- 等待全部结束,不管成功失败
- 永远不会走到 catch
- 返回数组:
{ status: 'fulfilled'/'rejected', value/reason } - 适合:批量上传、批量请求,要统计结果
4. race 和 any 区别?
-
Promise.race
- 谁第一个完成就返回谁
- 第一个失败 → 整体失败
- 常用于:超时控制
-
Promise.any
- 谁第一个成功就返回谁
- 失败会被忽略,直到出现成功
- 全部失败才整体失败
- 适合:多接口备用,只要一个能用就行
5. async 函数返回什么?
一定返回 Promise,不管你写不写 return。
规则:
- return 普通值 → 包装成
Promise.resolve(值) - return Promise → 直接返回它
- 抛错 → 变成
Promise.reject(错误)
6. await 后面跟普通函数会怎样?
- 会立即同步执行这个普通函数
- 返回值会被包装成
Promise.resolve(返回值) - await 下面的代码进入微任务,同步代码跑完再执行
- 不会真正等待异步,效果接近同步
7. Promise 中同步代码和微任务执行顺序?
new Promise((resolve) => { ... })内部代码 立即同步执行.then()/.catch()里的回调是微任务- 执行顺序:同步代码 > 微任务 > 宏任务
8. 手写 Promise.all(面试高频)
javascript
function PromiseAll(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) return reject(new Error('不是数组'))
const result = []
let count = 0
const len = promises.length
if (len === 0) return resolve([])
promises.forEach((p, index) => {
Promise.resolve(p).then(res => {
result[index] = res
count++
if (count === len) resolve(result)
}).catch(err => {
reject(err)
})
})
})
}
9. 如何中断 Promise?
Promise 本身不能取消,但可以模拟中断:
- 用 Promise.race 做超时中断
javascript
function withAbort(promise, timeout) {
const abort = new Promise((_, reject) => {
setTimeout(() => reject('abort'), timeout)
})
return Promise.race([promise, abort])
}
- 用标志位跳过逻辑
javascript
let aborted = false
async function fn() {
if (aborted) return
await request()
}
aborted = true // 中断
- 新标准:AbortController(fetch 可用)
javascript
const controller = new AbortController()
fetch('/api', { signal: controller.signal })
controller.abort() // 直接中断
10. 什么是宏任务 / 微任务?
宏任务(macrotask)
- 优先级低,每次执行一个
- 常见:
setTimeout/setInterval- ajax / fetch
- DOM 事件、UI 渲染
微任务(microtask)
- 优先级高,一次性清空队列
- 常见:
Promise.then/catch/finallyasync/await后面代码queueMicrotaskMutationObserver
执行顺序铁律
同步代码 → 微任务(清空) → 宏任务(一个) → 循环