Promise 原理与实现
Promise 基础概念
什么是 Promise
Promise 是 JavaScript 中处理异步操作的核心机制。在 Promise 出现之前,异步编程主要依赖回调函数,这在嵌套复杂时会形成"回调地狱"。Promise 提供了一种更优雅的方式来组织和管理异步代码——它代表一个未来某个时刻才会确定的值。
从本质上看,Promise 是一个状态机。它是一个对象,内部维护着异步操作的状态,并在状态确定后通知所有注册的观察者。
三种状态与不可逆转
Promise 有且仅有三种状态:
┌──────────┐ resolve(value) ┌───────────┐
│ │ ─────────────────────→ │ │
│ Pending │ │ Fulfilled │
│ (等待态) │ │ (成功态) │
│ │ │ │
└──────────┘ └───────────┘
│
│ reject(reason) ┌───────────┐
└───────────────────────────→ │ │
│ Rejected │
│ (失败态) │
│ │
└───────────┘- Pending(等待态):初始状态,既不是成功也不是失败
- Fulfilled(成功态):操作成功完成,Promise 持有一个确定的值(value)
- Rejected(失败态):操作失败,Promise 持有一个确定的原因(reason)
状态不可逆转是 Promise 最重要的设计原则之一。一旦状态从 Pending 变为 Fulfilled 或 Rejected,就永远不会再改变。这意味着:
const p = new Promise((resolve, reject) => {
resolve('success')
reject('error')
resolve('another success')
})
p.then(value => console.log(value))输出结果只有 success。第二次调用 reject 和第三次调用 resolve 都被忽略了,因为状态已经从 Pending 变为 Fulfilled,不可逆转。
这一特性保证了 Promise 的确定性——无论何时访问一个已经 settled 的 Promise,得到的结果都是一样的。
状态变更的微任务特性
Promise 的状态变更本身是同步的,但 then 注册的回调是以**微任务(Microtask)**的方式异步执行的:
console.log('1')
const p = new Promise((resolve) => {
console.log('2')
resolve('3')
console.log('4')
})
p.then(value => console.log(value))
console.log('5')输出顺序:1 → 2 → 4 → 5 → 3
executor 函数(传入 new Promise 的函数)是同步执行的,所以 2 和 4 会在同步流程中按顺序输出。而 then 的回调被放入微任务队列,等到当前同步代码全部执行完毕后才会运行,所以 3 最后输出。
then/catch/finally 链式调用原理
then 的核心机制
then 方法是 Promise 最核心的方法。它接收两个可选参数:
promise.then(onFulfilled, onRejected)then 的关键特性:每次调用都返回一个新的 Promise。这是链式调用的基础。
const p1 = Promise.resolve(1)
const p2 = p1.then(v => v + 1)
const p3 = p2.then(v => v + 1)
console.log(p1 === p2)
console.log(p2 === p3)两个输出都是 false,说明 then 每次都创建了新的 Promise 实例。
新 Promise 的状态由 then 的回调函数返回值决定:
Promise.resolve(1)
.then(v => v * 2)
.then(v => {
throw new Error('fail')
})
.then(
v => console.log('fulfilled:', v),
r => console.log('rejected:', r.message)
)输出:rejected: fail。第二个 then 的回调抛出异常,导致它返回的 Promise 变为 Rejected,于是第三个 then 的 onRejected 被触发。
值穿透
当 then 接收的不是函数时,会发生值穿透——值或状态会直接传递到下一个有效的 then:
Promise.resolve('hello')
.then(null)
.then(undefined)
.then(123)
.then(v => console.log(v))输出 hello。非函数参数被忽略,值一路穿透到最终的 then。
值穿透的内部机制等价于:
Promise.resolve('hello')
.then(v => v)
.then(v => v)
.then(v => v)
.then(v => console.log(v))当 onFulfilled 不是函数时,它会被替换为 v => v(恒等函数);当 onRejected 不是函数时,它会被替换为 r => { throw r }(抛出函数)。这保证了值和错误都能正确穿透。
错误冒泡
错误会沿着链一直向下传播,直到遇到一个错误处理函数:
Promise.reject('error')
.then(v => console.log('then1:', v))
.then(v => console.log('then2:', v))
.then(v => console.log('then3:', v))
.catch(r => console.log('caught:', r))输出 caught: error。三个 then 都没有提供 onRejected 回调,错误不断冒泡,最终被 catch 捕获。
这与 try/catch 的行为类似——错误会跳过所有不处理它的节点,直到被捕获。
catch 的本质
catch 本质上就是 then(null, onRejected) 的语法糖:
Promise.prototype.catch = function(onRejected) {
return this.then(null, onRejected)
}但 catch 与 then 的第二个参数有一个重要区别:catch 能捕获前一个 then 的 onFulfilled 回调中抛出的异常,而 then 的第二个参数只能捕获前一个 Promise 的错误。
Promise.resolve('ok').then(
v => { throw new Error('then error') },
r => console.log('onRejected:', r)
)
Promise.resolve('ok')
.then(v => { throw new Error('then error') })
.catch(r => console.log('catch:', r.message))第一个写法中 onRejected 不会被触发,因为异常发生在同一个 then 的 onFulfilled 中。第二个写法中 catch 能正确捕获异常,因为 catch 作用于 then 返回的新 Promise。
finally 的实现原理
finally 不管 Promise 最终状态如何都会执行,且它的回调不接收任何参数,也不改变 Promise 的值(除非抛出异常或返回 rejected Promise):
Promise.resolve(42)
.finally(() => console.log('cleanup'))
.then(v => console.log(v))输出:cleanup 然后 42。finally 透传了原始值。
Promise.resolve(42)
.finally(() => { throw new Error('oops') })
.then(v => console.log(v))
.catch(r => console.log('error:', r.message))输出 error: oops。finally 回调中抛出异常时会覆盖原始状态。
finally 的实现等价于:
Promise.prototype.finally = function(callback) {
return this.then(
value => Promise.resolve(callback()).then(() => value),
reason => Promise.resolve(callback()).then(() => { throw reason })
)
}Promise A+ 规范核心要点
Promise A+ 规范是 Promise 的行业标准,ES6 的 Promise 在此基础上扩展了更多 API。以下是规范的核心要点:
1. 术语
- Promise:一个拥有
then方法的对象或函数,其行为符合本规范 - thenable:一个定义了
then方法的对象或函数 - value:任何合法的 JavaScript 值(包括
undefined、thenable 或 Promise) - exception:使用
throw语句抛出的值 - reason:表示 Promise 被拒绝原因的值
2. Promise 状态
- Promise 必须处于 Pending、Fulfilled、Rejected 三种状态之一
- Pending 状态可以转变为 Fulfilled 或 Rejected
- Fulfilled 和 Rejected 状态不可再变更
- Fulfilled 状态必须有一个不可变的 value
- Rejected 状态必须有一个不可变的 reason
3. then 方法
Promise 必须提供 then 方法来访问当前或最终的值/原因:
promise.then(onFulfilled, onRejected)核心规则:
onFulfilled和onRejected都是可选的,如果不是函数则忽略onFulfilled必须在 Promise fulfilled 后调用,以 Promise 的值为第一个参数,且只能调用一次onRejected必须在 Promise rejected 后调用,以 Promise 的原因为第一个参数,且只能调用一次onFulfilled和onRejected只有在执行上下文栈仅包含平台代码时才可被调用(即异步执行)then可以在同一个 Promise 上被多次调用,回调按注册顺序依次执行then必须返回一个新的 Promise
4. Promise 解决过程(Resolution Procedure)
这是规范中最复杂也最关键的部分,定义为 [[Resolve]](promise, x),其中 x 是 onFulfilled 或 onRejected 的返回值:
┌─────────────────────────────────────────────────────────┐
│ [[Resolve]](promise2, x) │
├─────────────────────────────────────────────────────────┤
│ │
│ 1. x === promise2 │
│ → reject(TypeError) 防止循环引用 │
│ │
│ 2. x 是 Promise │
│ → 采用 x 的状态 │
│ │
│ 3. x 是对象或函数 │
│ → 尝试取 x.then │
│ → 如果 then 是函数,以 x 为 this 调用 │
│ → 如果 then 不是函数,fulfill(x) │
│ │
│ 4. x 不是对象也不是函数 │
│ → fulfill(x) │
│ │
└─────────────────────────────────────────────────────────┘这个解决过程保证了不同 Promise 实现之间的互操作性——只要实现了 then 方法的对象(thenable)都能被正确处理。
手写完整 Promise 实现
基础结构
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor(executor) {
this.status = PENDING
this.value = undefined
this.reason = undefined
this.onFulfilledCallbacks = []
this.onRejectedCallbacks = []
const resolve = (value) => {
if (value instanceof MyPromise) {
value.then(resolve, reject)
return
}
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
this.onFulfilledCallbacks.forEach(fn => fn())
}
}
const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
}关键设计点:
onFulfilledCallbacks和onRejectedCallbacks是数组,因为同一个 Promise 可以多次调用thenresolve内部检查value instanceof MyPromise,如果 resolve 的值本身是 Promise,则等待其 settled- executor 用 try/catch 包裹,确保同步异常能被捕获并 reject
resolvePromise —— 核心解决过程
这是 Promise A+ 规范中最复杂的部分,也是手写 Promise 的关键:
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'))
}
if (x instanceof MyPromise) {
x.then(
y => resolvePromise(promise2, y, resolve, reject),
reject
)
return
}
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 (e) {
if (called) return
called = true
reject(e)
}
} else {
resolve(x)
}
}详解:
- 循环引用检测:如果
then返回的 Promise 和回调返回值相同,形成循环引用,直接抛 TypeError - x 是 MyPromise 实例:递归解析,因为
then回调返回的 Promise 可能 resolve 另一个 Promise - x 是 thenable 对象:用
called标志位防止多次调用。这是为了兼容不规范的 thenable 实现——有些库可能同时调用 resolve 和 reject,或者多次调用 - x 是普通值:直接 resolve
完整的 then 方法
MyPromise.prototype.then = function(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
const promise2 = new MyPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
const rejectedMicrotask = () => {
queueMicrotask(() => {
try {
const x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if (this.status === FULFILLED) {
fulfilledMicrotask()
} else if (this.status === REJECTED) {
rejectedMicrotask()
} else if (this.status === PENDING) {
this.onFulfilledCallbacks.push(fulfilledMicrotask)
this.onRejectedCallbacks.push(rejectedMicrotask)
}
})
return promise2
}核心设计要点:
- 值穿透:当
onFulfilled不是函数时,默认为value => value;onRejected不是函数时,默认为reason => { throw reason } - 异步执行:使用
queueMicrotask确保回调异步执行,符合规范要求 - 返回新 Promise:
then总是返回promise2,链式调用的基础 - 三种状态处理:Fulfilled/Rejected 立即注册微任务;Pending 时将微任务推入回调数组,等待状态变更后执行
catch 和 finally
MyPromise.prototype.catch = function(onRejected) {
return this.then(null, onRejected)
}
MyPromise.prototype.finally = function(callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value),
reason => MyPromise.resolve(callback()).then(() => { throw reason })
)
}完整实现汇总
将上面所有部分组合起来就是一个完整的、符合 Promise A+ 规范的实现:
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'))
}
if (x instanceof MyPromise) {
x.then(
y => resolvePromise(promise2, y, resolve, reject),
reject
)
return
}
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 (e) {
if (called) return
called = true
reject(e)
}
} else {
resolve(x)
}
}
class MyPromise {
constructor(executor) {
this.status = PENDING
this.value = undefined
this.reason = undefined
this.onFulfilledCallbacks = []
this.onRejectedCallbacks = []
const resolve = (value) => {
if (value instanceof MyPromise) {
value.then(resolve, reject)
return
}
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
this.onFulfilledCallbacks.forEach(fn => fn())
}
}
const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
const promise2 = new MyPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
const rejectedMicrotask = () => {
queueMicrotask(() => {
try {
const x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if (this.status === FULFILLED) {
fulfilledMicrotask()
} else if (this.status === REJECTED) {
rejectedMicrotask()
} else if (this.status === PENDING) {
this.onFulfilledCallbacks.push(fulfilledMicrotask)
this.onRejectedCallbacks.push(rejectedMicrotask)
}
})
return promise2
}
catch(onRejected) {
return this.then(null, onRejected)
}
finally(callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value),
reason => MyPromise.resolve(callback()).then(() => { throw reason })
)
}
}Promise 静态方法手写实现
Promise.resolve
Promise.resolve 将一个值转换为 Promise。需要处理三种情况:
MyPromise.resolve = function(value) {
if (value instanceof MyPromise) {
return value
}
return new MyPromise((resolve) => {
resolve(value)
})
}如果传入的已经是 MyPromise 实例,直接返回(幂等性)。否则创建一个新的已 fulfilled 的 Promise。注意 resolve 内部已经处理了 thenable 的情况。
Promise.reject
Promise.reject 始终返回一个 rejected 的 Promise,不做任何解包:
MyPromise.reject = function(reason) {
return new MyPromise((_, reject) => {
reject(reason)
})
}与 resolve 不同,即使 reason 是一个 Promise,也不会解包,而是直接作为 reject 的原因。
Promise.all
等待所有 Promise 都 fulfilled,或者任意一个 rejected:
MyPromise.all = function(promises) {
return new MyPromise((resolve, reject) => {
const result = []
let count = 0
const promiseArr = Array.from(promises)
if (promiseArr.length === 0) {
return resolve(result)
}
promiseArr.forEach((p, index) => {
MyPromise.resolve(p).then(
value => {
result[index] = value
count++
if (count === promiseArr.length) {
resolve(result)
}
},
reason => {
reject(reason)
}
)
})
})
}关键点:
- 使用
result[index]而非push,保证结果顺序与输入顺序一致 - 使用计数器
count而非检查result.length,因为数组的稀疏特性可能导致length不准确 - 任意一个 reject 就立即 reject 整个 Promise
- 空数组直接 resolve
- 使用
MyPromise.resolve(p)包装,兼容非 Promise 值
Promise.allSettled
等待所有 Promise settled(无论成功或失败),返回每个结果的状态和值:
MyPromise.allSettled = function(promises) {
return new MyPromise((resolve) => {
const result = []
let count = 0
const promiseArr = Array.from(promises)
if (promiseArr.length === 0) {
return resolve(result)
}
promiseArr.forEach((p, index) => {
MyPromise.resolve(p).then(
value => {
result[index] = { status: 'fulfilled', value }
count++
if (count === promiseArr.length) {
resolve(result)
}
},
reason => {
result[index] = { status: 'rejected', reason }
count++
if (count === promiseArr.length) {
resolve(result)
}
}
)
})
})
}allSettled 永远不会 reject。每个结果是 { status, value } 或 { status, reason } 的格式。
Promise.race
返回第一个 settled 的 Promise 的结果(无论成功或失败):
MyPromise.race = function(promises) {
return new MyPromise((resolve, reject) => {
const promiseArr = Array.from(promises)
promiseArr.forEach(p => {
MyPromise.resolve(p).then(resolve, reject)
})
})
}race 的实现非常简洁。由于 Promise 状态不可逆转,第一个 settle 的 Promise 会调用 resolve 或 reject,后续的调用都会被忽略。
注意:如果传入空数组,返回的 Promise 将永远处于 Pending 状态。
Promise.any
返回第一个 fulfilled 的 Promise 的值。如果所有 Promise 都 rejected,则 reject 一个 AggregateError:
MyPromise.any = function(promises) {
return new MyPromise((resolve, reject) => {
const errors = []
let count = 0
const promiseArr = Array.from(promises)
if (promiseArr.length === 0) {
return reject(new AggregateError([], 'All promises were rejected'))
}
promiseArr.forEach((p, index) => {
MyPromise.resolve(p).then(
value => {
resolve(value)
},
reason => {
errors[index] = reason
count++
if (count === promiseArr.length) {
reject(new AggregateError(errors, 'All promises were rejected'))
}
}
)
})
})
}any 可以看作是 all 的"镜像"——all 是任意一个 reject 就失败,any 是任意一个 fulfill 就成功。
Promise 并发控制
在实际场景中,我们经常需要同时发起大量异步请求,但又不能让它们全部并行(可能导致服务端压力过大、浏览器连接数耗尽等问题)。Promise 并发控制就是限制同时执行的异步任务数量。
基于池模型的并发控制
function concurrencyLimit(tasks, limit) {
return new Promise((resolve, reject) => {
const results = []
let running = 0
let index = 0
let settled = false
function next() {
while (running < limit && index < tasks.length) {
const currentIndex = index++
running++
Promise.resolve(tasks[currentIndex]())
.then(value => {
results[currentIndex] = value
})
.catch(err => {
if (!settled) {
settled = true
reject(err)
}
})
.finally(() => {
running--
if (!settled) {
if (index >= tasks.length && running === 0) {
settled = true
resolve(results)
} else {
next()
}
}
})
}
}
next()
})
}使用示例:
const tasks = [
() => fetch('/api/1').then(r => r.json()),
() => fetch('/api/2').then(r => r.json()),
() => fetch('/api/3').then(r => r.json()),
() => fetch('/api/4').then(r => r.json()),
() => fetch('/api/5').then(r => r.json()),
]
concurrencyLimit(tasks, 2).then(results => {
console.log(results)
})执行流程:
时间轴 →
Task 1: ████████
Task 2: ██████████████
Task 3: ████████████
Task 4: ██████
Task 5: ██████████████
并发数: 2 2 2 2 1最多同时执行 2 个任务,一个完成立即启动下一个。
基于类的并发调度器
更通用的实现,支持动态添加任务:
class Scheduler {
constructor(maxConcurrency) {
this.maxConcurrency = maxConcurrency
this.running = 0
this.queue = []
}
add(task) {
return new Promise((resolve, reject) => {
this.queue.push({ task, resolve, reject })
this.run()
})
}
run() {
while (this.running < this.maxConcurrency && this.queue.length > 0) {
const { task, resolve, reject } = this.queue.shift()
this.running++
Promise.resolve(task())
.then(resolve, reject)
.finally(() => {
this.running--
this.run()
})
}
}
}使用示例:
const scheduler = new Scheduler(2)
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms))
const addTask = (time, value) => {
scheduler.add(() => delay(time).then(() => {
console.log(value)
return value
}))
}
addTask(1000, '1')
addTask(500, '2')
addTask(300, '3')
addTask(400, '4')输出顺序:2 → 3 → 1 → 4
0ms 500ms 800ms 1000ms 1200ms
|------|------|------|------|
Task1: ████████████████████ → 1000ms 输出 '1'
Task2: ██████████ → 500ms 输出 '2'
Task3: ██████ → 800ms 输出 '3'
Task4: ████████████ → 1200ms 输出 '4' (非900ms,因为等Task2完成才开始)实际上 Task3 在 500ms(Task2 完成时)才开始,在 800ms 完成;Task4 也在 500ms 开始(因为并发数恢复到 1 后立即调度),在 900ms 完成。但 Task1 在 1000ms 才完成,所以输出顺序是 2(500ms) → 3(800ms) → 4(900ms) → 1(1000ms)。
修正后的输出顺序应为:2 → 3 → 4 → 1。
async/await 的本质
Generator 基础
要理解 async/await,首先需要理解 Generator 函数。Generator 是一种可以暂停和恢复执行的特殊函数:
function* gen() {
const a = yield 1
const b = yield 2
return a + b
}
const it = gen()
console.log(it.next())
console.log(it.next(10))
console.log(it.next(20))输出:
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 30, done: true }关键特性:
yield暂停函数执行,将yield后面的值作为next()的返回值next(val)恢复函数执行,并将val作为上一个yield表达式的返回值- Generator 函数调用不会立即执行,而是返回一个迭代器
async/await = Generator + 自动执行器
async/await 本质上是 Generator 函数 + 自动执行器的语法糖。对比以下两种写法:
async function fetchData() {
const res1 = await fetch('/api/1')
const data1 = await res1.json()
const res2 = await fetch('/api/2?id=' + data1.id)
const data2 = await res2.json()
return data2
}等价的 Generator 写法:
function* fetchData() {
const res1 = yield fetch('/api/1')
const data1 = yield res1.json()
const res2 = yield fetch('/api/2?id=' + data1.id)
const data2 = yield res2.json()
return data2
}区别在于:async/await 自带执行器,Generator 需要手动驱动或借助执行器。
自动执行器的实现
function co(generatorFn) {
return function(...args) {
const gen = generatorFn.apply(this, args)
return new Promise((resolve, reject) => {
function step(nextFn) {
let next
try {
next = nextFn()
} catch (e) {
return reject(e)
}
if (next.done) {
return resolve(next.value)
}
Promise.resolve(next.value).then(
value => step(() => gen.next(value)),
reason => step(() => gen.throw(reason))
)
}
step(() => gen.next())
})
}
}使用方式:
const fetchData = co(function* () {
const res1 = yield fetch('/api/1')
const data1 = yield res1.json()
return data1
})
fetchData().then(data => console.log(data))执行器的核心逻辑:
- 调用
gen.next(value)恢复 Generator 执行,获取yield出的值 - 将
yield出的值包装为 Promise(Promise.resolve(next.value)) - 等待 Promise settled:fulfilled 则将值传回 Generator 继续执行;rejected 则向 Generator 抛出错误
- 递归执行,直到 Generator 返回
{ done: true } - 最终结果作为外层 Promise 的 resolve 值
手写 async/await 的 polyfill
一个更完整的 polyfill,模拟 Babel 对 async/await 的转译:
function asyncToGenerator(generatorFn) {
return function(...args) {
const gen = generatorFn.apply(this, args)
return new Promise((resolve, reject) => {
function step(key, arg) {
let result
try {
result = gen[key](arg)
} catch (e) {
return reject(e)
}
const { value, done } = result
if (done) {
resolve(value)
} else {
Promise.resolve(value).then(
val => step('next', val),
err => step('throw', err)
)
}
}
step('next', undefined)
})
}
}Babel 转译示例——原始代码:
async function example() {
const a = await Promise.resolve(1)
const b = await Promise.resolve(2)
return a + b
}Babel 转译后(简化版):
function example() {
return asyncToGenerator(function* () {
const a = yield Promise.resolve(1)
const b = yield Promise.resolve(2)
return a + b
}).call(this)
}async 关键字被替换为 asyncToGenerator 包裹的 Generator 函数,await 被替换为 yield。
async 函数的返回值
async 函数始终返回 Promise:
async function fn1() { return 42 }
async function fn2() { return Promise.resolve(42) }
async function fn3() { throw new Error('oops') }
fn1().then(v => console.log(v))
fn2().then(v => console.log(v))
fn3().catch(e => console.log(e.message))fn1返回Promise.resolve(42)fn2返回值是 Promise,会被自动解包(类似resolve的行为)fn3抛出异常等价于返回Promise.reject(new Error('oops'))
Promise 常见陷阱与反模式
1. 回调地狱的变形——Promise 嵌套
getUserInfo().then(user => {
getOrders(user.id).then(orders => {
getOrderDetail(orders[0].id).then(detail => {
console.log(detail)
})
})
})这实际上是把回调地狱换了个皮,完全没有利用 Promise 链式调用的优势。
正确写法:
getUserInfo()
.then(user => getOrders(user.id))
.then(orders => getOrderDetail(orders[0].id))
.then(detail => console.log(detail))或使用 async/await:
async function loadDetail() {
const user = await getUserInfo()
const orders = await getOrders(user.id)
const detail = await getOrderDetail(orders[0].id)
console.log(detail)
}2. 错误吞没
const p = new Promise((resolve, reject) => {
reject(new Error('something went wrong'))
})如果不对 p 调用 catch,这个错误会被"吞没"——在 Node.js 中会触发 unhandledRejection 事件,在浏览器中会在控制台输出警告,但不会中断程序执行。
更隐蔽的情况:
Promise.resolve()
.then(() => {
JSON.parse('invalid json')
})then 回调中的同步异常会被自动捕获并变为 rejection,但如果没有后续的 catch,这个错误就会静默消失。
最佳实践:始终在 Promise 链的末尾添加 catch,或者使用全局错误处理:
window.addEventListener('unhandledrejection', event => {
console.error('Unhandled rejection:', event.reason)
})
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled rejection:', reason)
})3. 不必要的 await
async function getData() {
const data = await fetch('/api/data')
return await data.json()
}最后一个 await 是不必要的,因为 async 函数的返回值本身就会被 Promise.resolve 包装:
async function getData() {
const data = await fetch('/api/data')
return data.json()
}但有一个例外——如果外面有 try/catch 且需要捕获 data.json() 可能产生的异常,那么 await 是必要的:
async function getData() {
try {
const data = await fetch('/api/data')
return await data.json()
} catch (e) {
return { error: e.message }
}
}如果不 await,data.json() 的异常不会被 try/catch 捕获,因为 Promise 在 return 之后才会 reject。
4. 在循环中错误使用 await
async function processItems(items) {
const results = []
for (const item of items) {
const result = await processItem(item)
results.push(result)
}
return results
}这会导致所有请求串行执行。如果这些请求之间没有依赖关系,应该并行处理:
async function processItems(items) {
const results = await Promise.all(
items.map(item => processItem(item))
)
return results
}但如果需要控制并发,则应使用前文所述的并发控制方案。
5. Promise.all 的脆弱性
const results = await Promise.all([
fetchUser(),
fetchOrders(),
fetchSettings()
])任何一个失败都会导致整体失败,即使其他请求已经成功。如果业务允许部分失败,应该使用 Promise.allSettled:
const results = await Promise.allSettled([
fetchUser(),
fetchOrders(),
fetchSettings()
])
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('成功:', result.value)
} else {
console.log('失败:', result.reason)
}
})6. 混淆 then 和 catch 的返回值
Promise.reject('error')
.catch(r => {
console.log('caught:', r)
})
.then(v => {
console.log('then:', v)
})输出:
caught: error
then: undefined很多人以为 catch 之后链就终止了,但实际上 catch 也返回一个新的 Promise。如果 catch 的回调没有抛出异常,返回的 Promise 就是 fulfilled(值为 catch 回调的返回值,此处为 undefined),于是后续的 then 会正常执行。
7. 构造函数中的异步错误
new Promise((resolve, reject) => {
setTimeout(() => {
throw new Error('async error')
}, 0)
})这个错误不会被 Promise 捕获。executor 的 try/catch 只能捕获同步异常。setTimeout 回调中的异常发生在新的调用栈中,Promise 的 try/catch 早已退出。
正确做法:
new Promise((resolve, reject) => {
setTimeout(() => {
try {
throw new Error('async error')
} catch (e) {
reject(e)
}
}, 0)
})经典面试题
面试题一:Promise 执行顺序
console.log('start')
setTimeout(() => {
console.log('setTimeout')
}, 0)
Promise.resolve()
.then(() => {
console.log('promise1')
})
.then(() => {
console.log('promise2')
})
new Promise((resolve) => {
console.log('promise3')
resolve()
}).then(() => {
console.log('promise4')
})
console.log('end')输出顺序:
start
promise3
end
promise1
promise4
promise2
setTimeout分析:
- 执行同步代码:输出
start setTimeout回调注册到宏任务队列Promise.resolve().then(...)注册微任务 A(输出promise1)new Promise的 executor 同步执行,输出promise3;resolve()后then注册微任务 B(输出promise4)- 输出
end - 同步代码执行完毕,清空微任务队列:先执行微任务 A(输出
promise1),微任务 A 的then注册微任务 C(输出promise2),再执行微任务 B(输出promise4),再执行微任务 C(输出promise2) - 微任务队列清空,执行宏任务:输出
setTimeout
面试题二:then 返回值与链式传递
Promise.resolve(1)
.then(v => {
console.log(v)
return v + 1
})
.then(v => {
console.log(v)
throw new Error('error')
})
.catch(e => {
console.log(e.message)
return 4
})
.then(v => {
console.log(v)
})
.then(v => {
console.log(v)
return Promise.resolve(6)
})
.then(v => {
console.log(v)
})输出顺序:
1
2
error
4
undefined
6分析:
Promise.resolve(1)→then接收v = 1,输出1,返回2- 下一个
then接收v = 2,输出2,抛出错误 - 错误冒泡到
catch,输出error,返回4(catch返回的 Promise 变为 fulfilled) - 下一个
then接收v = 4,输出4,没有显式返回值,默认返回undefined - 下一个
then接收v = undefined,输出undefined,返回Promise.resolve(6) Promise.resolve(6)被解包,下一个then接收v = 6,输出6
面试题三:async/await 与事件循环
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')输出顺序:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout分析:
- 输出
script start setTimeout注册宏任务- 调用
async1(),输出async1 start - 执行
await async2(),先同步执行async2(),输出async2 await相当于async2().then(() => { console.log('async1 end') }),将async1 end的输出注册为微任务 Aasync1让出执行权,继续执行同步代码new Promise的 executor 同步执行,输出promise1;resolve()后then注册微任务 B- 输出
script end - 同步代码执行完毕,清空微任务队列:执行微任务 A(输出
async1 end),执行微任务 B(输出promise2) - 执行宏任务:输出
setTimeout
面试题四:并发控制实战
实现一个函数,接收 URL 数组和最大并发数,返回所有请求结果:
async function fetchWithConcurrency(urls, maxConcurrency) {
const results = new Array(urls.length)
let currentIndex = 0
async function worker() {
while (currentIndex < urls.length) {
const index = currentIndex++
try {
results[index] = await fetch(urls[index]).then(r => r.json())
} catch (e) {
results[index] = { error: e.message }
}
}
}
const workers = Array.from(
{ length: Math.min(maxConcurrency, urls.length) },
() => worker()
)
await Promise.all(workers)
return results
}这种实现使用了 worker 池模式:创建 maxConcurrency 个 worker,每个 worker 不断从任务队列(通过 currentIndex 隐式维护)中取任务执行,直到所有任务完成。
面试题五:Promise 实现红绿灯交替
实现一个红绿灯,红灯亮 3 秒,绿灯亮 2 秒,黄灯亮 1 秒,循环交替:
function light(color, duration) {
return new Promise(resolve => {
console.log(color)
setTimeout(resolve, duration)
})
}
async function trafficLight() {
while (true) {
await light('红灯', 3000)
await light('绿灯', 2000)
await light('黄灯', 1000)
}
}
trafficLight()这道题考察的是如何用 Promise + async/await 实现重复的异步时序控制。while(true) 不会阻塞主线程,因为每次 await 都会让出执行权。
总结
┌─────────────────────────────────────────────────────────────────┐
│ Promise 知识体系 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 基础概念 规范实现 高级应用 │
│ ┌──────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ 三种状态 │ │ Promise A+ │ │ 并发控制 │ │
│ │ 状态不可逆 │ │ resolvePromise│ │ 调度器实现 │ │
│ │ 微任务执行 │ │ then 链 │ │ worker 池模式 │ │
│ └──────────┘ │ 异步处理 │ └──────────────────┘ │
│ └──────────────┘ │
│ 链式调用 语法糖 │
│ ┌──────────┐ 静态方法 ┌──────────────────┐ │
│ │ 值穿透 │ ┌──────────────┐ │ Generator │ │
│ │ 错误冒泡 │ │ resolve/reject│ │ 自动执行器 │ │
│ │ then/catch│ │ all/allSettled│ │ async/await 本质 │ │
│ │ finally │ │ race/any │ │ polyfill 实现 │ │
│ └──────────┘ └──────────────┘ └──────────────────┘ │
│ │
│ 常见陷阱:嵌套 Promise | 错误吞没 | 不必要的 await │
│ 串行循环 | all 脆弱性 | 异步错误未捕获 │
└─────────────────────────────────────────────────────────────────┘Promise 的设计哲学是将异步操作的结果从时间维度中解耦出来——不管异步操作何时完成,我们都可以用统一的方式来处理它的结果。理解 Promise 的内部实现,不仅有助于在面试中脱颖而出,更重要的是能在日常开发中写出更健壮、更高效的异步代码。