Promise 不会?看这里:史上最通俗易懂的 Promise 入门与实现
Category(分类): JavaScript Status: 已整理(2026)
本文保留原文从回调函数、Promise 基础用法、
then/catch、all/race到手写 Promise/A+ 解析过程的完整主线。原文中的状态、关键字、变量名拼写错误,缺少返回值和不完整构造器已修正;同时补充原生 Promise 与 Promise/A+、thenable、微任务、取消请求和现代静态方法的边界。
一、什么是 Promise,解决什么问题
Promise 是表示异步操作最终结果的对象。它的状态通常有:
pending:等待中;fulfilled:成功完成;rejected:失败。
Promise 的状态只能从 pending 变化一次,变化后不能再次改变。注意“Promise 对象存在”不等于“异步操作已经开始”:new Promise(executor) 会立即同步执行 executor,异步性通常来自 executor 内部的定时器、网络请求、文件操作等:
console.log('before')
const promise = new Promise(resolve => {
console.log('executor runs now')
setTimeout(() => resolve('done'), 10)
})
promise.then(value => console.log(value))
console.log('after')
// before
// executor runs now
// after
// done
Promise 主要帮助我们:
- 用链式数据流替代深层回调嵌套;
- 统一表达成功、失败和异常传播;
- 组合多个异步操作;
- 让
async/await建立在可组合的 Promise 语义上。
Promise 本身不是“自动把同步代码变成异步”的魔法,也不会自动取消网络请求或解决所有并发竞态。
二、回调函数和回调地狱
简单回调可以完成异步通知:
function runIfValid(value, callback) {
if (value > 10 && typeof callback === 'function') {
callback(value)
}
}
runIfValid(11, value => {
console.log('callback:', value)
})
当多个操作必须按前后顺序嵌套时,代码可能变得难以维护:
loadUser((userError, user) => {
if (userError) return handleError(userError)
loadOrders(user.id, (orderError, orders) => {
if (orderError) return handleError(orderError)
loadProducts(orders, (productError, products) => {
if (productError) return handleError(productError)
render(user, products)
})
})
})
Promise 可以把顺序关系写成链:
loadUser()
.then(user => loadOrders(user.id))
.then(orders => loadProducts(orders))
.then(products => render(products))
.catch(handleError)
function render(products) {
console.log(products)
}
function handleError(error) {
console.error(error)
}
Promise 让控制流更平坦,但业务仍需要明确错误、取消、重试、超时和并发策略。
三、原生 Promise 基础用法
3.1 创建 Promise
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true
if (success) {
resolve('我是成功结果')
} else {
reject(new Error('操作失败'))
}
}, 100)
})
executor 接收 resolve 和 reject 两个函数:
- 调用
resolve(value)表示成功; - 调用
reject(reason)表示失败; - executor 抛出的异常会自动使 Promise rejected;
- 第一次状态转换生效,后续调用无效。
推荐用 Error 对象表示异常原因,而不是直接 reject 字符串:
const failed = new Promise((resolve, reject) => {
reject(new Error('network unavailable'))
})
failed.catch(error => {
console.log(error.message) // network unavailable
})
3.2 then 链式调用
promise
.then(value => {
console.log('resolved:', value)
return value.length
})
.then(length => {
console.log('length:', length)
})
.catch(error => {
console.error('rejected:', error)
})
每一个 then 都返回新的 Promise。处理函数返回的普通值会传给下一个成功处理函数;返回 Promise 或 thenable 时,后续会等待它;抛错则进入后续拒绝路径:
Promise.resolve(1)
.then(value => value + 1)
.then(value => new Promise(resolve => {
setTimeout(() => resolve(value * 2), 10)
}))
.then(value => console.log(value)) // 4
如果某一步不返回值,下一个成功处理函数得到 undefined:
Promise.resolve('data')
.then(value => {
console.log(value)
// 没有 return
})
.then(value => console.log(value)) // undefined
3.3 reject 和错误处理
const randomPromise = new Promise((resolve, reject) => {
const number = Math.ceil(Math.random() * 10)
if (number <= 5) {
resolve(number)
} else {
reject(new Error(`数字太大:${number}`))
}
})
randomPromise.then(
value => console.log('resolved:', value),
error => console.log('rejected:', error.message)
)
then 的第二个参数只处理前一个 Promise 的 rejection;它不会捕获同一个 then 的成功处理器中抛出的异常。链尾使用 catch 通常更清晰:
randomPromise
.then(value => {
console.log(value)
return processValue(value)
})
.catch(error => {
console.error('请求或处理失败:', error)
})
function processValue(value) {
return value * 2
}
catch(onRejected) 等价于 then(undefined, onRejected):
promise.catch(onRejected)
// 等价于
promise.then(undefined, onRejected)
如果成功处理器返回 new Error(),那是 fulfilled 的普通值;只有 throw new Error() 或 return Promise.reject(error) 才会进入 rejected:
Promise.resolve()
.then(() => new Error('普通返回值'))
.then(value => console.log(value instanceof Error)) // true
Promise.resolve()
.then(() => { throw new Error('真正拒绝') })
.catch(error => console.log(error.message)) // 真正拒绝
3.4 finally
finally 不接收成功值或拒绝原因,通常用于无论成功失败都要执行的清理:
fetch('/api/data')
.then(response => response.json())
.catch(error => {
console.error(error)
throw error
})
.finally(() => {
console.log('hide loading indicator')
})
如果 finally 回调抛错或返回 rejected Promise,它会覆盖前面的结果;否则原来的值或原因会继续向后传递。
四、Promise 的并发组合
4.1 Promise.all
Promise.all 接收可迭代对象,全部 fulfilled 才 fulfilled;任意一个 rejected 就 rejected。结果数组的顺序与输入顺序一致,不与完成先后有关:
const userPromise = fetch('/api/user').then(response => response.json())
const settingsPromise = fetch('/api/settings').then(response => response.json())
Promise.all([userPromise, settingsPromise])
.then(([user, settings]) => {
console.log(user, settings)
})
.catch(error => {
console.error('任一请求失败:', error)
})
空数组的结果是立即 fulfilled 的空数组:
Promise.all([]).then(values => console.log(values)) // []
原文用于说明资源并行加载的思路仍然成立,但 Flash 已过时;现代场景可以是图片、字体、接口或模块资源。
4.2 Promise.race
Promise.race 在输入中第一个 settled 的 Promise 出现时结束:第一个 fulfilled 则成功,第一个 rejected 则失败。它不会取消其他任务:
function delay(value, milliseconds) {
return new Promise(resolve => {
setTimeout(() => resolve(value), milliseconds)
})
}
Promise.race([
delay('slow', 100),
delay('fast', 10)
]).then(console.log) // fast
原文通过图片请求和超时 Promise 演示 race:
function requestImage(url) {
return new Promise((resolve, reject) => {
const image = new Image()
image.onload = () => resolve(image)
image.onerror = () => reject(new Error('图片加载失败'))
image.src = url
})
}
function timeout(milliseconds) {
return new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('图片请求超时')), milliseconds)
})
}
Promise.race([
requestImage('/assets/image.png'),
timeout(5000)
]).then(
image => console.log(image),
error => console.error(error.message)
)
如果需要真正取消 fetch,应使用 AbortController:
async function fetchWithTimeout(url, milliseconds = 5000) {
const controller = new AbortController()
const timer = setTimeout(() => controller.abort(), milliseconds)
try {
const response = await fetch(url, { signal: controller.signal })
return response
} finally {
clearTimeout(timer)
}
}
4.3 其他静态方法
const tasks = [
Promise.resolve('success'),
Promise.reject(new Error('failed'))
]
Promise.allSettled(tasks).then(console.log)
// [{ status: 'fulfilled', value: 'success' },
// { status: 'rejected', reason: Error('failed') }]
Promise.any([
Promise.reject(new Error('first failure')),
Promise.resolve('first success')
]).then(console.log) // first success
Promise.allSettled:等待全部结束,不因单项失败而提前拒绝;Promise.any:第一个成功就成功,全部失败时以AggregateError拒绝;Promise.resolve:创建/采纳一个 fulfilled 或 thenable 结果;Promise.reject:创建 rejected Promise。
五、原文插图
原文的 Promise 状态和 race 演示图已下载到本地:

图片来源:原始图片。

图片来源:原始图片。

图片来源:原始图片。

图片来源:原始图片。
六、手写 Promise:先实现状态和回调队列
手写 Promise 的目标不是替换原生实现,而是理解以下机制:
- executor 同步执行;
- 状态只能完成一次;
- pending 时暂存成功/失败处理器;
- settled 后按顺序调度处理器;
then返回新的 Promise;- 处理函数的结果通过解析过程传给新 Promise。
原文第一版代码中的等待状态拼写不一致,应统一为 pending。此外,真正的 Promise 处理器应异步调度;只在状态改变时同步 forEach 回调,会与原生 Promise 语义不同。
6.1 Promise/A+ 解析过程
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
reject(new TypeError('Promise cannot resolve to itself'))
return
}
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
let called = false
let then
try {
then = x.then
} catch (error) {
reject(error)
return
}
if (typeof then === 'function') {
try {
then.call(
x,
value => {
if (called) return
called = true
resolvePromise(promise2, value, resolve, reject)
},
reason => {
if (called) return
called = true
reject(reason)
}
)
} catch (error) {
if (called) return
called = true
reject(error)
}
return
}
}
resolve(x)
}
这里的 called 很重要:不可信 thenable 可能同时调用成功和失败回调,也可能多次调用;解析过程只接受第一次结果。读取 x.then 也可能触发 getter 并抛错,因此必须捕获异常。
6.2 一个可运行的 MyPromise
下面的实现保留原文的类、状态数组、链式 then、catch、all、race 和 A+ deferred 思路,并修正状态一次性、thenable、空数组、异常和自引用问题。它用于学习,不是完整的原生 Promise polyfill:
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor(executor) {
this.status = PENDING
this.value = undefined
this.reason = undefined
this.handlers = []
let alreadyResolved = false
const fulfill = value => {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
this.flush()
}
const rejectInternal = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
this.flush()
}
const resolveValue = value => {
if (value === this) {
rejectInternal(new TypeError('Promise cannot resolve to itself'))
return
}
if (value !== null &&
(typeof value === 'object' || typeof value === 'function')) {
let then
try {
then = value.then
} catch (error) {
rejectInternal(error)
return
}
if (typeof then === 'function') {
let called = false
try {
then.call(
value,
nextValue => {
if (called) return
called = true
resolveValue(nextValue)
},
reason => {
if (called) return
called = true
rejectInternal(reason)
}
)
} catch (error) {
if (!called) {
called = true
rejectInternal(error)
}
}
return
}
}
fulfill(value)
}
const resolve = value => {
if (alreadyResolved) return
alreadyResolved = true
resolveValue(value)
}
const reject = reason => {
if (alreadyResolved) return
alreadyResolved = true
rejectInternal(reason)
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
flush() {
const handlers = this.handlers.splice(0)
for (const handler of handlers) {
queueMicrotask(handler)
}
}
then(onFulfilled, onRejected) {
const fulfilled = typeof onFulfilled === 'function'
? onFulfilled
: value => value
const rejected = typeof onRejected === 'function'
? onRejected
: reason => { throw reason }
let resolveNext
let rejectNext
const promise2 = new MyPromise((resolve, reject) => {
resolveNext = resolve
rejectNext = reject
})
const run = () => {
try {
const callback = this.status === FULFILLED ? fulfilled : rejected
const input = this.status === FULFILLED ? this.value : this.reason
resolveNext(callback(input))
} catch (error) {
rejectNext(error)
}
}
if (this.status === PENDING) {
this.handlers.push(run)
} else {
queueMicrotask(run)
}
return promise2
}
catch(onRejected) {
return this.then(undefined, onRejected)
}
finally(onFinally) {
if (typeof onFinally !== 'function') return this.then()
return this.then(
value => MyPromise.resolve(onFinally()).then(() => value),
reason => MyPromise.resolve(onFinally()).then(() => {
throw reason
})
)
}
static resolve(value) {
return value instanceof MyPromise
? value
: new MyPromise(resolve => resolve(value))
}
static reject(reason) {
return new MyPromise((resolve, reject) => reject(reason))
}
static all(iterable) {
const values = Array.from(iterable)
return new MyPromise((resolve, reject) => {
if (values.length === 0) {
resolve([])
return
}
const result = new Array(values.length)
let remaining = values.length
values.forEach((value, index) => {
MyPromise.resolve(value).then(item => {
result[index] = item
remaining -= 1
if (remaining === 0) resolve(result)
}, reject)
})
})
}
static race(iterable) {
return new MyPromise((resolve, reject) => {
for (const value of iterable) {
MyPromise.resolve(value).then(resolve, reject)
}
})
}
static deferred() {
const deferred = {}
deferred.promise = new MyPromise((resolve, reject) => {
deferred.resolve = resolve
deferred.reject = reject
})
return deferred
}
}
6.3 使用和自测
MyPromise.resolve(1)
.then(value => value + 1)
.then(value => console.log(value)) // 2
MyPromise.all([
MyPromise.resolve('a'),
2,
new MyPromise(resolve => setTimeout(() => resolve('c'), 10))
]).then(values => console.log(values)) // ['a', 2, 'c']
const deferred = MyPromise.deferred()
deferred.promise.then(value => console.log(value))
deferred.resolve('deferred value')
可以用以下边界检查实现:
const thenable = {
then(resolve, reject) {
resolve(1)
reject(new Error('ignored'))
}
}
MyPromise.resolve(thenable).then(value => console.log(value)) // 1
let self
self = MyPromise.resolve().then(() => self)
self.catch(error => console.log(error.name)) // TypeError
MyPromise.all([]).then(values => console.log(values)) // []
七、原文代码中的问题说明
原文抓取代码中有一些会导致语法错误或语义错误的片段,整理如下:
- 等待状态的拼写与判断值不一致,应统一为
pending; - 函数关键字、
resolve和变量名存在拼写错误,应统一修正; - 自引用判断应比较
promise2与x; - 静态方法调用应使用
MyPromise.resolve(item); - 自定义
Promise.resolve的返回表达式缺少闭合括号; Promise.all必须处理普通值、空 iterable,并按输入索引保存结果;Promise.race也必须使用Promise.resolve采纳普通值和 thenable;- 仅用
instanceof Promise不能支持其他实现或 thenable; - 直接在
resolve时同步执行所有回调,不符合原生 Promise 的异步处理器语义; - 只在 pending 分支保存回调,还要保证已 settled 后新增的
then也会异步执行; - Promise 的 resolving function 在第一次调用时就锁定,即使它正在等待一个 pending thenable;
- 自定义实现不应命名为
Promise覆盖全局原生 Promise,示例使用MyPromise更安全。
八、Promise、async/await 和错误传播
async 函数总是返回 Promise;await 会等待一个可被 Promise 解析的值:
async function loadData() {
const response = await fetch('/api/data')
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
return response.json()
}
loadData()
.then(data => console.log(data))
.catch(error => console.error(error))
await 让链式逻辑看起来像同步代码,但不会阻塞线程;抛出的异常会让 async 函数返回的 Promise rejected。多个独立任务不要无意中串行等待:
async function loadPage() {
const userPromise = fetch('/api/user').then(response => response.json())
const settingsPromise = fetch('/api/settings').then(response => response.json())
const [user, settings] = await Promise.all([userPromise, settingsPromise])
return { user, settings }
}
九、总结
- Promise 是异步结果的状态容器,但构造器 executor 会同步执行;
- Promise 本身不能消除所有回调、竞态、取消和超时问题;
then返回新 Promise,普通返回值、异常和 thenable 会决定下一环;catch用于统一处理链上的拒绝,finally用于清理;all、race、allSettled、any适用于不同的并发策略;race只决定结果,不会自动取消输掉的任务;- 手写 Promise 的难点在状态一次性、异步回调、thenable 解析、自引用和异常处理;
- Promise/A+ 主要规定
then和解析过程,原生 Promise 还包括更多 ECMAScript 与宿主语义; - 生产代码优先使用原生 Promise、
async/await和AbortController,手写实现用于学习和测试。