深入理解Promise

前言

Promise 是 JavaScript 异步编程的核心,几乎所有现代前端项目都绕不开它。本文将带你从基础到底层实现,全面理解 Promise 的工作机制。

一、为什么需要 Promise

在 Promise 出现之前,异步操作依赖回调函数,层层嵌套会形成”回调地狱”:

1
2
3
4
5
6
7
8
// 回调地狱
getUser(id, (user) => {
getOrders(user.id, (orders) => {
getOrderDetail(orders[0].id, (detail) => {
console.log(detail) // 层层嵌套,可读性极差
})
})
})

Promise 用链式调用解决这个问题:

1
2
3
4
5
getUser(id)
.then(user => getOrders(user.id))
.then(orders => getOrderDetail(orders[0].id))
.then(detail => console.log(detail))
.catch(err => console.error(err))

二、三种状态

Promise 只有三种互斥状态,一旦确定就不可改变:

1
2
3
pending ──────► fulfilled(已成功)
│
└──────────► rejected(已失败)
1
2
3
4
5
6
7
8
9
10
11
const p1 = new Promise(() => {})          // pending
const p2 = Promise.resolve('ok') // fulfilled
const p3 = Promise.reject('fail') // rejected

// 状态一旦变更就定型
const p = new Promise((resolve, reject) => {
resolve('first')
reject('second') // 无效,已经 fulfilled
resolve('third') // 无效,已经 fulfilled
})
console.log(p) // Promise { 'first' }

三、手写 Promise

理解原理最好的方式就是写一遍。

基础骨架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class MyPromise {
constructor(executor) {
this.state = 'pending' // 状态
this.value = undefined // 成功值
this.reason = undefined // 失败原因
this.onFulfilled = [] // 成功回调队列
this.onRejected = [] // 失败回调队列

const resolve = (value) => {
if (this.state !== 'pending') return
this.state = 'fulfilled'
this.value = value
this.onFulfilled.forEach(fn => fn())
}

const reject = (reason) => {
if (this.state !== 'pending') return
this.state = 'rejected'
this.reason = reason
this.onRejected.forEach(fn => fn())
}

try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
}

then 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e }

const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
// 用 setTimeout 模拟微任务
setTimeout(() => {
try {
const x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
}

if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
}

if (this.state === 'pending') {
// 异步时先存起来,等状态变了再执行
this.onFulfilled.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
})
this.onRejected.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
})
}
})

return promise2
}

链式调用核心:resolvePromise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
function resolvePromise(promise2, x, resolve, reject) {
// 防止循环引用
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected'))
}

// 如果 x 是 MyPromise 实例
if (x instanceof MyPromise) {
x.then(resolve, reject)
}
// 如果 x 是对象或函数(thenable)
else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
let called = false
try {
const then = x.then
if (typeof then === 'function') {
then.call(x,
y => {
if (called) return
called = true
resolvePromise(promise2, y, resolve, reject)
},
r => {
if (called) return
called = true
reject(r)
}
)
} else {
resolve(x)
}
} catch (err) {
if (called) return
called = true
reject(err)
}
}
// 普通值直接 resolve
else {
resolve(x)
}
}

called 标志是 Promise/A+ 规范的硬要求,防止 thenable 中多次调用 resolve/reject。

四、静态方法实现

Promise.resolve / Promise.reject

1
2
3
4
5
6
7
8
static resolve(value) {
if (value instanceof MyPromise) return value
return new MyPromise(resolve => resolve(value))
}

static reject(reason) {
return new MyPromise((_, reject) => reject(reason))
}

Promise.all

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static all(promises) {
return new MyPromise((resolve, reject) => {
const result = []
let count = 0
if (promises.length === 0) return resolve(result)

promises.forEach((p, i) => {
MyPromise.resolve(p).then(value => {
result[i] = value
count++
if (count === promises.length) resolve(result)
}, reject) // 任何一个失败,全部失败
})
})
}

Promise.race

1
2
3
4
5
6
7
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(p => {
MyPromise.resolve(p).then(resolve, reject)
})
})
}

Promise.allSettled

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static allSettled(promises) {
return new MyPromise((resolve) => {
const result = []
let count = 0

promises.forEach((p, i) => {
MyPromise.resolve(p).then(
value => { result[i] = { status: 'fulfilled', value }; count++ },
reason => { result[i] = { status: 'rejected', reason }; count++ }
).finally(() => {
if (count === promises.length) resolve(result)
})
})
})
}

Promise.any

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static any(promises) {
return new MyPromise((resolve, reject) => {
const errors = []
let count = 0

promises.forEach((p, i) => {
MyPromise.resolve(p).then(resolve, err => {
errors[i] = err
count++
if (count === promises.length) {
reject(new AggregateError(errors, 'All promises were rejected'))
}
})
})
})
}

五、async / await

async/await 是 Promise 的语法糖,让异步代码看起来像同步:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Promise 写法
fetchUser().then(user => {
return fetchOrders(user.id).then(orders => {
console.log(orders)
})
})

// async/await 写法
async function getOrders() {
try {
const user = await fetchUser()
const orders = await fetchOrders(user.id)
console.log(orders)
} catch (err) {
console.error(err)
}
}

核心规则:

  • async 函数始终返回 Promise
  • await 只能在 async 函数内使用
  • await 会暂停函数执行,等待 Promise 完成
  • 错误用 try/catch 捕获

await 执行顺序陷阱(高频面试题)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
async function async1() {
console.log('async1 start') // 2
await async2()
console.log('async1 end') // 6(等价于 then 回调)
}

async function async2() {
console.log('async2') // 3
}

console.log('script start') // 1

setTimeout(() => console.log('setTimeout'), 0) // 8

async1()

new Promise(resolve => {
console.log('promise1') // 4
resolve()
}).then(() => console.log('promise2')) // 7

console.log('script end') // 5

// 输出:script start → async1 start → async2 → promise1 → script end
// → async1 end → promise2 → setTimeout

await 后面的代码相当于 .then() 中的回调,会被放入微任务队列。

六、Vue3 中 Promise 的应用

Promise 在 Vue3 中无处不在,从 setup 顶层 await 到路由守卫再到请求封装,都有它的身影。

1. <script setup> 顶层 await

Vue3 的 <script setup> 支持顶层 await,配合 <Suspense> 可实现组件级异步加载:

1
2
3
4
5
6
7
8
9
10
11
12
13
<script setup>
// 组件渲染前等待数据加载完成
const user = await fetch('/api/user').then(r => r.json())
const posts = await fetch('/api/posts').then(r => r.json())
// 串行加载(不推荐),实际应改为并行
</script>

<template>
<h1>{{ user.name }}</h1>
<ul>
<li v-for="p in posts" :key="p.id">{{ p.title }}</li>
</ul>
</template>

搭配 Suspense 处理加载状态:

1
2
3
4
5
6
7
8
9
<!-- 父组件 -->
<Suspense>
<template #default>
<AsyncDashboard />
</template>
<template #fallback>
<Loading /> <!-- 数据加载完之前展示 -->
</template>
</Suspense>

2. 路由守卫中的 Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// router.js
const router = createRouter({ /* ... */ })

// 全局前置守卫 — 检查登录状态
router.beforeEach(async (to, from) => {
// next() 可以接收 Promise
if (to.meta.requiresAuth) {
const user = await fetchUser().catch(() => null)
if (!user) return { name: 'login' }
}
})

// 路由懒加载 — import() 返回 Promise
const routes = [
{
path: '/dashboard',
component: () => import('@/views/Dashboard.vue') // Promise<Component>
}
]

3. 请求封装与并发控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// api.js — Axios 封装
import axios from 'axios'
import { ref } from 'vue'

// 请求去重 Hook
export function useRequest(fetcher) {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
let lastPromise = null

function execute(...args) {
// 取消上一个未完成的请求(竞态处理)
const current = fetcher(...args)
lastPromise = current

loading.value = true
error.value = null

return current
.then(res => {
// 只处理最新的请求结果
if (current === lastPromise) {
data.value = res
}
return res
})
.catch(err => {
if (current === lastPromise) {
error.value = err
}
throw err
})
.finally(() => {
if (current === lastPromise) {
loading.value = false
}
})
}

return { data, loading, error, execute }
}

4. 并行请求 Promise.all

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<script setup>
import { ref, onMounted } from 'vue'

const user = ref(null)
const config = ref(null)
const stats = ref(null)
const loading = ref(true)

onMounted(async () => {
try {
// 三个请求并行,总耗时 = max(请求1, 请求2, 请求3)
const [u, c, s] = await Promise.all([
fetch('/api/user'),
fetch('/api/config'),
fetch('/api/stats')
].map(p => p.then(r => r.json())))

user.value = u
config.value = c
stats.value = s
} catch (err) {
console.error('数据加载失败', err)
} finally {
loading.value = false
}
})
</script>

5. watchEffect 中的异步清理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { ref, watchEffect } from 'vue'

const userId = ref(1)

// watchEffect 回调支持 async,自动追踪响应式依赖
watchEffect(async (onCleanup) => {
let cancelled = false

// 注册清理函数
onCleanup(() => { cancelled = true })

const data = await fetchUser(userId.value)
// userId 变化 → 重新执行 → onCleanup 取消上一次未完成的请求
if (!cancelled) {
console.log('用户数据:', data)
}
})

// 切换 userId 时,旧的请求结果会被忽略
setTimeout(() => userId.value = 2, 1000)

6. nextTick 本质是 Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Vue3 中 nextTick 返回 Promise
import { nextTick, ref } from 'vue'

const count = ref(0)

async function increment() {
count.value++
// await nextTick 等价于 .then,等 DOM 更新后执行
await nextTick()
console.log(document.querySelector('.count').textContent) // 已更新
}

// nextTick 本质:
// Vue2: new Promise(resolve => resolve()).then(cb)
// Vue3: Promise.resolve().then(cb)

七、常见陷阱

陷阱 错误示例 正确做法
忘记 return .then(() => fetchData()) .then(() => return fetchData())
嵌套 hell Promise 里嵌套 Promise 链式 .then() 或 async/await
不处理错误 .then(fn) 无 catch .then(fn).catch(handle)
并行写成串行 for 循环挨个 await Promise.all(arr.map(fn))
循环中 async(期望并行) await 在循环体里 先 map 再用 Promise.all

总结

  • Promise 有三种状态 pending/fulfilled/rejected,一旦变更不可逆
  • then 返回新 Promise 才能链式调用,resolvePromise 是核心
  • resolvePromise 中 called 标志是 Promise/A+ 规范的严格要求
  • Promise.all/race/allSettled/any 各有适用场景
  • async/await 是 Promise 的语法糖,await 后面是微任务
  • 永远不要忘记错误处理 — .catch() 或 try/catch

内容由 AI 生成,仅供参考。本文发布于 码上学习,转载请注明出处。