前言
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(() => {}) const p2 = Promise.resolve('ok') const p3 = Promise.reject('fail')
const p = new Promise((resolve, reject) => { resolve('first') reject('second') resolve('third') }) console.log(p)
|
三、手写 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(() => { 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')) }
if (x instanceof MyPromise) { x.then(resolve, reject) } 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) } } 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
| fetchUser().then(user => { return fetchOrders(user.id).then(orders => { console.log(orders) }) })
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') await async2() console.log('async1 end') }
async function async2() { console.log('async2') }
console.log('script start')
setTimeout(() => console.log('setTimeout'), 0)
async1()
new Promise(resolve => { console.log('promise1') resolve() }).then(() => console.log('promise2'))
console.log('script end')
|
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
| const router = createRouter({ })
router.beforeEach(async (to, from) => { if (to.meta.requiresAuth) { const user = await fetchUser().catch(() => null) if (!user) return { name: 'login' } } })
const routes = [ { path: '/dashboard', component: () => import('@/views/Dashboard.vue') } ]
|
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
| import axios from 'axios' import { ref } from 'vue'
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 (onCleanup) => { let cancelled = false
onCleanup(() => { cancelled = true })
const data = await fetchUser(userId.value) if (!cancelled) { console.log('用户数据:', data) } })
setTimeout(() => userId.value = 2, 1000)
|
6. nextTick 本质是 Promise
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { nextTick, ref } from 'vue'
const count = ref(0)
async function increment() { count.value++ await nextTick() console.log(document.querySelector('.count').textContent) }
|
七、常见陷阱
| 陷阱 |
错误示例 |
正确做法 |
| 忘记 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 生成,仅供参考。本文发布于 码上学习,转载请注明出处。