在 JavaScript 中,异步操作是非常常见的场景(如文件读取、网络请求、定时器等)。早期异步编程依赖回调函数实现,当多个异步操作存在依赖关系时,会出现多层嵌套的回调函数,这种代码结构被称为回调地狱。
回调地狱的核心弊端:代码嵌套层级深、可读性极差、难以维护和调试。
// 回调地狱示例:多层嵌套定时器
setTimeout(() => {
console.log("延迟 1 秒输出");
setTimeout(() => {
console.log("延迟 2 秒输出");
setTimeout(() => {
console.log("延迟 3 秒输出");
}, 3000);
}, 2000);
}, 1000);为了解决回调地狱的问题,ES 6(ECMAScript 2015)中正式推出了 Promise 特性。
Promise 是 JavaScript 官方提供的异步编程解决方案,它是一个用于处理异步操作的对象,能够让异步代码的写法更简洁、逻辑更清晰。
Promise 实例有且仅有三种状态,状态一旦变更就永久凝固、不可逆转:
待定(pending):初始状态,异步操作正在执行,未得到结果
已完成(fulfilled):异步操作成功执行,调用 resolve 后触发
已拒绝(rejected):异步操作执行失败,调用 reject 后触发
状态变更只有两种可能:
pending → fulfilled(成功)
pending → rejected(失败)
Promise 是一个构造函数 通过 new Promise() 创建实例,每个实例对应一个异步操作。
执行器函数(同步执行) 创建 Promise 时必须传入一个函数,该函数会同步立即执行,内部编写异步逻辑。
原型方法 .then() 所有 Promise 实例都可以通过原型链调用 .then(),用于指定成功 / 失败的回调。
原型方法 .catch() / .finally() 用于统一捕获错误,以及定义无论成功失败都执行的逻辑。
Promise 构造函数接收一个执行器函数作为参数,执行器函数有两个形参:
resolve:异步操作成功时调用,将状态改为 fulfilled
reject:异步操作失败时调用,将状态改为 rejected
// 基础 Promise 示例:封装定时器
const p = new Promise((resolve, reject) => {
// 异步操作逻辑
setTimeout(() => {
// 模拟成功
resolve("异步操作执行成功");
// 模拟失败
// reject(new Error("异步操作执行失败"));
}, 1000);
});.then() 处理结果.then(成功回调, 失败回调) 用于接收异步操作的结果:
成功回调:必选,接收 resolve 传递的参数
失败回调:可选,接收 reject 传递的参数
p.then(
(result) => { console.log("成功:", result); },
(error) => { console.log("失败:", error); }
);Node.js 原生 fs 模块不支持 Promise 写法,可安装第三方包 then-fs 快速体验 Promise 风格的文件读取:
npm install then-fs基础读取文件(无序,并行执行):
import thenFs from "then-fs";
thenFs.readFile('./files/1.txt', 'utf8').then(data => console.log(data), err => console.log(err));
thenFs.readFile('./files/2.txt', 'utf8').then(data => console.log(data), err => console.log(err));.then() 方法默认返回一个新的 Promise 实例,因此可以持续链式调用 .then(),完美解决回调地狱。
通过链式调用,保证异步操作按顺序执行(上一个完成后再执行下一个):
import thenFs from "then-fs";
thenFs
.readFile("./files/1.txt", "utf8")
.then((data) => {
console.log(data);
// 返回新的 Promise 实例
return thenFs.readFile("./files/2.txt", "utf8");
})
.then((data) => {
console.log(data);
return thenFs.readFile("./files/3.txt", "utf8");
})
.then((data) => {
console.log(data);
});异步操作的异常必须通过 Promise 提供的方法捕获,无法使用 try/catch 直接捕获。
.catch()在链式调用末尾添加 .catch(),可以捕获整个链条中任意位置的错误:
import thenFs from "then-fs";
thenFs
.readFile("./files/1.txt", "utf8")
.then((data) => thenFs.readFile("./files/2.txt", "utf8"))
.then((data) => thenFs.readFile("./files/3.txt", "utf8"))
.catch((err) => {
console.log("文件读取失败:", err.message);
});将 .catch() 放在链式调用前方,错误不会中断后续 .then() 执行:
thenFs
.readFile("./files/1.txt", "utf8")
.catch((err) => console.log("1.txt 读取失败:", err))
.then((data) => thenFs.readFile("./files/2.txt", "utf8"))
.then((data) => console.log(data));.finally().finally() 是 ES 2018 新增方法,无论异步操作成功或失败,都会执行,常用于清理资源:
p.then(result => {}).catch(err => {}).finally(() => {
console.log("无论成功失败,我都会执行");
});Promise 构造函数自带 4 个常用静态方法,用于处理多个异步任务。
等待机制:并行执行所有异步操作,全部成功才会触发 .then(),任意一个失败则直接触发 .catch()。
import thenFs from "then-fs";
const promiseArr = [
thenFs.readFile("./files/1.txt", "utf8"),
thenFs.readFile("./files/2.txt", "utf8"),
];
Promise.all(promiseArr)
.then(([data1, data2]) => console.log(data1, data2))
.catch(err => console.log("读取失败:", err));赛跑机制:并行执行异步操作,只要有一个完成(成功 / 失败),立即触发对应回调。
Promise.race(promiseArr)
.then(data => console.log("最先读取完成的文件:", data))
.catch(err => console.log(err));兜底机制:无论成功 / 失败,都会等待所有异步操作执行完毕,返回所有任务的执行结果(无失败情况)。
成功优先机制:只要有一个异步操作成功,就触发 .then();全部失败才触发 .catch()。
我们可以基于原生 fs 模块,手动封装支持 Promise 风格的文件读取方法,掌握 Promise 封装的核心逻辑。
定义函数并返回 Promise 实例
在执行器函数中编写异步逻辑
根据结果调用 resolve/reject
导出模块供外部使用
// fileUtil.js
import fs from 'fs';
/**
* 封装 Promise 风格的文件读取方法
* @param {string} fPath 文件路径
* @returns {Promise} Promise 实例
*/
function getFile(fPath) {
return new Promise((resolve, reject) => {
// 原生文件读取异步操作
fs.readFile(fPath, "utf8", (err, data) => {
if (err) return reject(err);
resolve(data);
});
});
}
// 导出方法
export default { getFile };import fileUtil from "./fileUtil.js";
// 调用封装的 Promise 方法
fileUtil.getFile("./files/1.txt")
.then(data => console.log("读取成功:", data))
.catch(err => console.log("读取失败:", err.message));执行器函数是同步执行的 new Promise(() => {}) 中的函数会立即同步执行,.then() 中的回调是异步执行的。
.catch() 本质 .catch() 等价于 .then(null, 失败回调),是语法糖。
值穿透特性 如果 .then() 没有传入回调函数,Promise 会自动将结果传递给下一个 .then()。
状态唯一性 一个 Promise 实例的状态只能变更一次,多次调用 resolve/reject 无效。
结合所学知识,实现顺序读取文件 + 错误捕获 + 最终清理的完整逻辑:
import fileUtil from "./fileUtil.js";
fileUtil.getFile("./files/1.txt")
.then(data => {
console.log(data);
return fileUtil.getFile("./files/2.txt");
})
.then(data => {
console.log(data);
return fileUtil.getFile("./files/3.txt");
})
.then(data => console.log(data))
.catch(err => console.log("操作终止:", err.message))
.finally(() => console.log("文件读取流程执行完毕"));Promise 核心解决回调地狱,让异步代码扁平化、可维护
三种状态不可逆,resolve/reject 控制状态变更
链式调用实现顺序异步操作,.catch() 统一捕获错误
静态方法 all/race 适用于多异步任务场景
自定义 Promise 封装是日常开发的核心技能