本专栏聚焦Promise的核心原理与高级应用,包含: ✓ Promise A+规范深度解读 ✓ 手写实现与源码分析 ✓ 异步编程设计模式 ✓ 性能调优与错误处理
适合有JavaScript基础,希望深入异步编程的开发者。我们将用最少的篇幅,讲透最核心的知识。
引言:为什么需要Promise?
在本篇开始之前,先想象一下这样的场景:我们需要从服务器获取用户信息,然后根据用户信息获取订单列表,接着根据订单获取商品详情,最后渲染页面。在早期的JavaScript中,我们可能需要写出这样的代码:
getUserInfo(userId, function(userInfo) {
getOrderList(userInfo.id, function(orders) {
orders.forEach(function(order) {
getProductDetail(order.productId, function(product) {
renderProduct(product, function() {
// 后面可能还有更多回调处理函数
});
});
});
});
});
这就是臭名昭著的 回调地狱(Callback Hell) 。代码不仅难以阅读和维护,错误处理也变得异常困难。为了解决这个问题,Promise应运而生。
同步与异步
在正式介绍Promise之前,我们先来了解一下JavaScript中的同步与异步的相关概念。
同步编程
同步编程对应内存中顺序执行的处理器指令,其特点是代码按顺序执行,每行代码必须等待上一行执行完成后才能进行下一行代码的执行。
console.log('步骤1'); // 打印 步骤1
let result = doSomething(); // 这里会阻塞线程
console.log('步骤2'); // 必须等待doSomething()执行完成后,才打印 步骤2
异步编程
异步编程类似于系统中断,当前进程外部的实体可以触发代码执行,即允许某些操作在后台执行,而主线程可以继续执行其他任务。
console.log('开始');
fetchDataAsync(function(data) { // 异步操作
console.log('数据获取完成:', data);
});
console.log('结束');
上述代码的执行结果是:
1、打印 开始
2、打印 结束
3、打印 数据获取完成
即: console.log('结束'); 代码会在 console.log('开始'); 执行完成后立即执行,不用等待 fetchDataAsync 方法执行完成。
早期的异步编程
定时器:setTimeout & setInterval
定时器 setTimeout 和 setInterval 应该是应用的最早的,也是最广泛的异步API,至今仍在使用。
setTimeout:延迟执行
console.log('开始执行');
// setTimeout的基本使用
setTimeout(function () {
console.log('1秒后执行');
}, 1000);
console.log('立即执行');
// 清除定时器
const timerId = setTimeout(() => {
console.log('这个不会执行');
}, 2000);
clearTimeout(timerId);
上述代码的执行结果是:
1、打印 开始执行
2、打印 立即执行
3、打印 1秒后执行
值得注意的是:
const timerId = setTimeout(() => {})这段代码中,console.log('这个不会执行');这段代码永远不会执行,也不会打印,这是因为clearTimeout()函数立即取消了定时器,所以回调函数根本没有机会执行。
setInterval:间隔执行
let count = 0;
// 每隔1秒执行一次
const intervalId = setInterval(() => {
count++;
console.log(count);
if (count >= 5) {
clearInterval(intervalId); // 清除定时器
console.log('定时器已停止');
}
}, 1000);
上述代码执行结果是:每秒执行1次,执行5次后,打印 定时器已停止 ,程序结束。
Node.js风格的错误优先回调
这是Node.js早期API的标准模式,影响了整个Node.js生态系统。
const fs = require('fs');
// 经典的错误优先回调模式
fs.readFile('example.txt', 'utf8', function(err, data) {
if (err) {
// 错误处理
console.error('读取文件失败:', err.message);
return;
}
// 成功处理
console.log('文件内容:', data);
// 嵌套回调:开始回调地狱
fs.writeFile('output.txt', data.toUpperCase(), function(err) {
if (err) {
console.error('写入文件失败:', err);
return;
}
console.log('文件写入成功');
// 更多嵌套...
fs.readFile('output.txt', 'utf8', function(err, newData) {
if (err) {
console.error('再次读取失败:', err);
return;
}
console.log('转换后的内容:', newData);
});
});
});
眼尖的同学应该已经发现,上述代码开始产生回调地狱了。
Promise
为了解决回调模式的缺陷,Promise在2015年作为ES6规范的一部分正式加入JavaScript语言。它代表了一个异步操作的最终完成(或失败)及其结果值。
Promise A+规范
Promise A+规范是JavaScript Promise的行业标准,它详细定义了Promise的行为,确保了所有Promise实现之间的互操作性,即Promise类型。其核心目标是:
- 互操作性:不同实现的Promise可以无缝协作。
- 可预测性:Promise的行为在任何实现中都是一致的。
- 可靠性:提供健壮的异步编程基础。
Promise核心概念:三大状态与两大结果
理解Promise,首先要掌握它的状态机模型。一个Promise对象有三种状态:pending(待定)、fulfilled(兑现)、rejected(拒绝)。通过Promise的状态来代表是否完成,pending表示尚未开始或正在执行,fulfilled表示成功完成,rejected表示没有成功完成,即失败。
注:对于
fulfilled这一状态,不同资料翻译的结果不太一样,有的资料也将其称为“解决”,即resolved。
我们来看一个简单的Promise示例:
// Promise的三种状态:
// 1. pending(进行中): 初始状态
// 2. fulfilled(已成功): resolve()被调用
// 3. rejected(已失败): reject()被调用
// 创建Promise对象
const myPromise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const success = true; // 模拟操作是否成功
if (success) {
resolve('操作成功!'); // 状态变为fulfilled
} else {
reject(new Error('操作失败!')); // 状态变为rejected
}
}, 1000);
});
Promise的两个关键特性:
- 状态不可逆:一旦状态从pending变为fulfilled或rejected,就永远不会再改变。
- 值不可变:一旦Promise被决议(settled),它的值就固定不变了。
关于这两个特性,在后面的文章中会详细讲解。
Promise核心API
本篇只是简单介绍Promise中各个API,各API的详解及源码解析在后面的文章中会详细介绍。
| API | 类型 | 作用描述 |
|---|---|---|
| 构造函数 | 静态 | 创建新的Promise实例,包装异步操作,将回调转为Promise |
| .then() | 实例方法 | 添加fulfilled和rejected状态回调,处理异步成功/失败结果,链式调用核心 |
| .catch() | 实例方法 | 添加rejected状态回调(错误处理),捕获Promise链中的错误 |
| .finally() | 实例方法 | 无论成功失败都会执行的回调,用于清理资源,重置状态 |
| Promise.resolve() | 静态方法 | 创建已解决的Promise ,将值转为fulfilled,快速创建成功状态 |
| Promise.reject() | 静态方法 | 创建已拒绝的Promise,将值转为rejected,快速创建失败状态 |
| Promise.all() | 静态方法 | 所有Promise都执行成功才算成功(并行),有一个失败都算失败 |
| Promise.allSettled() | 静态方法 | 所有Promise执行完成后返回结果,需要知道每个操作结果(无论成败) |
| Promise.race() | 静态方法 | 第一个完成的Promise决定结果(竞速) |
| Promise.any() | 静态方法 | 第一个成功的Promise决定结果 多源请求,任一成功即可 |
结语
本文主要介绍了Promise的相关概念,对于文章中错误的地方或者有任何问题,欢迎在评论区留言分享!