# Promise 那些事儿
# Promise 的历史由来
Promise概念最早由Daniel P. Friedman和David Wise在1976年提出,并在1977年与Gary H. MacAlistair共同发表的论文《The Impact of Applicative Programming on Multiprocessing》中详细阐述。
在JavaScript中的发展历程:
早期实现:
- 2007年:Dojo框架首次在JavaScript中实现Promise模式
- 2009年:CommonJS项目制定了Promises/A规范
标准化进程:
- 2012年:Promises/A+规范发布,成为事实标准
- 2015年:ES6(ES2015)正式将Promise纳入语言标准
现代发展:
- 2017年:Async/Await语法糖基于Promise实现
- 2021年:Promise.any等新方法加入ES2021
Promise解决问题:
- 回调地狱(Callback Hell)
- 异步流程控制困难
- 错误处理复杂
- 代码可读性差
# JavaScript 中的 Promise
const promise = new Promise((hello, world) => {
console.log("init");
setTimeout(() => {
console.log("------------->pending");
hello(); // 状态改变,仅此一次
console.log("fulfilled");
world();
return;
console.log("after return 不会执行");
}, 0);
console.log("pending------------->");
});
console.log("同步");
promise
.then(() => {
console.log("then");
})
.catch(() => {
console.log("catch");
});
// init
// 同步
// fulfilled
// then
# Promise 的 A+规范
Promise A+规范核心要点:
# 状态机
Promise的状态机(State Machine)是Promise实现异步控制的核心机制,它具有以下特点:
三种确定状态:
- Pending(等待态):初始状态,既没有被兑现也没有被拒绝
- Fulfilled(已兑现):操作成功完成
- Rejected(已拒绝):操作失败
状态转换规则:
- 状态只能从Pending变为Fulfilled或Rejected
- 状态一旦改变就不可逆转(不可再变回Pending或其他状态)
- 状态改变是原子性的(要么成功要么失败)
状态与值的关系:
- Fulfilled状态必须有一个值(value)
- Rejected状态必须有一个原因(reason)
enum PromiseState {
PENDING = 'pending',
FULFILLED = 'fulfilled',
REJECTED = 'rejected'
}
class MyPromise<T> {
private state: PromiseState = PromiseState.PENDING;
private value: T | undefined;
private reason: any;
// ... other implementations
}
状态机的重要性:
- 保证异步操作的确定性
- 提供清晰的执行流程控制
- 防止竞态条件(Race Condition)
- 为链式调用提供基础
# then 方法
then方法是Promise的核心方法,它定义了Promise的异步处理流程。根据A+规范,then方法具有以下特性:
方法签名:
- 接收两个可选参数:onFulfilled和onRejected回调函数
- 返回一个新的Promise对象,支持链式调用
参数处理规则:
- 如果onFulfilled不是函数,则忽略并向下传递值
- 如果onRejected不是函数,则忽略并向下传递错误
执行时机:
- 当Promise状态变为fulfilled时调用onFulfilled
- 当Promise状态变为rejected时调用onRejected
- 回调函数总是异步执行(微任务队列)
interface Thenable<T> {
then<U>(
onFulfilled?: (value: T) => U | Thenable<U>,
onRejected?: (reason: any) => U | Thenable<U>
): Promise<U>;
}
class MyPromise<T> implements Thenable<T> {
then<U>(
onFulfilled?: (value: T) => U | Thenable<U>,
onRejected?: (reason: any) => U | Thenable<U>
): Promise<U> {
// ... implementation
}
}
then方法的重要性:
- 实现Promise链式调用的基础
- 提供统一的异步处理接口
- 支持值穿透和错误冒泡
- 保证回调函数的执行顺序
# 值穿透
值穿透(Value Penetration)是Promise A+规范中的重要特性,指当then方法的回调函数不是函数类型时,Promise会将值"穿透"到链式调用的下一个then中。
const p = new Promise((resolve) => resolve(42));
// 值穿透示例
p.then()
.then()
.then((value) => {
console.log(value); // 42
});
值穿透的实现原理:
- 规范要求then方法的参数必须是函数,如果不是函数则创建默认函数
- 默认的onFulfilled函数会将接收到的值原样返回
- 默认的onRejected函数会将错误原因原样抛出 这种行为保证了Promise链式调用的连续性,即使中间有空的then()调用也不会中断值的传递。
# 链式调用
Promise链式调用(Chaining)是Promise的核心特性之一,它允许我们将多个异步操作按顺序连接起来。链式调用的关键点在于:
then方法返回新Promise:
- 每个then()方法都会返回一个新的Promise对象
- 这使得我们可以连续调用多个then()方法
值传递机制:
- 前一个then()回调的返回值会作为参数传递给下一个then()
- 如果返回的是Promise,会等待该Promise解决后再继续
错误冒泡:
- 链式调用中的错误会一直向下传递,直到被catch()捕获
- 这使得错误处理更加集中和方便
function fetchData(): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => resolve('data'), 1000);
});
}
fetchData()
.then((data) => {
console.log(data);
return data.toUpperCase();
})
.then((upperData) => {
console.log(upperData);
});
链式调用的优势:
- 避免回调地狱(Callback Hell)
- 使异步代码具有同步代码的可读性
- 便于错误处理和流程控制
# Promise 的应用
# Promise 串行执行
- 基本串行执行 - 使用async/await顺序执行
async function serialExecution(): Promise<void> {
const tasks = [
() => new Promise(resolve => setTimeout(() => resolve(1), 1000)),
() => new Promise(resolve => setTimeout(() => resolve(2), 500)),
() => new Promise(resolve => setTimeout(() => resolve(3), 300))
];
for (const task of tasks) {
const result = await task();
console.log(result); // 依次输出: 1 (1秒后), 2 (0.5秒后), 3 (0.3秒后)
}
}
- 带错误处理的串行执行
async function serialWithErrorHandling(): Promise<void> {
const tasks = [
() => new Promise(resolve => resolve(1)),
() => new Promise((_, reject) => reject('error')),
() => new Promise(resolve => resolve(3))
];
const results: Array<number | string> = [];
for (const task of tasks) {
try {
results.push(await task());
} catch (error) {
results.push(error as string);
}
}
console.log(results); // [1, "error", 3]
}
- 带超时处理的串行执行
async function serialWithTimeout(): Promise<void> {
const tasks = [
() => new Promise(resolve => setTimeout(() => resolve(1), 1000)),
() => new Promise(resolve => setTimeout(() => resolve(2), 500)),
() => new Promise(resolve => setTimeout(() => resolve(3), 300))
];
for (const task of tasks) {
try {
// 为每个任务设置超时
const result = await Promise.race([
task(),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), 800)
)
]);
console.log(result);
} catch (error) {
console.error('任务超时:', error.message);
}
}
}
// 执行结果:
// 1 (正常完成)
// Error: 任务超时: Timeout (第二个任务超时)
// 3 (正常完成)
# Promise 并发执行
async function concurrentAll(): Promise<void> {
const promises = [
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3)
];
try {
const results = await Promise.all(promises);
console.log(results); // [1, 2, 3]
} catch (error) {
console.error(error);
}
}
# 并发控制
p-limit 是一个用于控制 Promise 并发执行数量的实用库,特别适合需要限制并发请求数量的场景
import pLimit from 'p-limit';
// 创建一个并发限制为2的limit函数
const limit = pLimit(2);
const tasks = [
limit(() => fetchSomething('foo')),
limit(() => fetchSomething('bar')),
limit(() => fetchSomething('baz'))
];
// 执行所有任务
const result = await Promise.all(tasks);
console.log(result); // [result1, result2, result3]
使用场景:
- API请求限流
- 文件批量处理
- 数据库操作并发控制
- 爬虫任务调度
# 学习资料
Promise 的前世今生 (opens new window)
Futures_and_promises - Wikipedia (opens new window)
Future 与 promise - 维基百科 (opens new window)
JavaScript 中的异步冒险:Promises (opens new window)