JavaScript Promise 完整入门教程
在 JavaScript 中,代码执行分为同步与异步两种模式。同步代码自上而下依次执行,而上异步代码(定时器、网络请求、文件读写)不会阻塞主线程,是前端开发的核心能力。
在 ES6 之前,开发者主要通过回调函数处理异步结果。当多个异步任务串行执行时,会出现多层函数嵌套,代码层级混乱、无法复用、异常难以排查,该问题被称为回调地狱。
为解决该行业痛点,ES6 正式标准化推出 Promise,统一 JavaScript 异步编程规范。本文将从基础概念、状态原理、语法拆解、实战案例、高阶方法、语法糖全方位精讲,所有代码附带逐行解析。
一、Promise 核心概念与底层原理
1.1 Promise 定义与核心作用
Promise 是 JavaScript 内置的异步操作容器对象,专门用于封装异步操作,预先定义异步操作成功、失败的处理逻辑。
其核心作用可总结为四点,也是项目中必须使用 Promise 的原因:
-
解决传统回调函数的嵌套问题,实现代码扁平化
-
统一异步操作的成功、失败处理逻辑,规范代码结构
-
支持链式调用,轻松实现异步任务串行执行
-
支持批量处理异步任务,适配复杂业务场景
1.2 Promise 三种不可逆状态
每一个 Promise 实例在生命周期内,只会存在三种状态,且状态一旦变更,永久固定,无法二次修改,这是 Promise 最核心的底层特性。
-
pending(等待态):初始默认状态。Promise 实例创建完成、异步操作未执行结束时,均处于该状态。
-
fulfilled(成功态):异步操作正常执行完毕,无异常。状态由 pending 切换为 fulfilled,触发
.then()回调。 -
rejected(失败态):异步操作报错、超时、逻辑异常。状态由 pending 切换为 rejected,触发
.catch()回调。
固定流转规则:pending 只能单向切换为 fulfilled 或 rejected,不存在成功转失败、失败转成功的情况。
1.3 核心回调参数解析
创建 Promise 实例时,传入的执行器回调函数会接收两个系统内置参数,二者均为可执行函数,用于手动变更 Promise 状态:
-
resolve():将 Promise 状态从 pending 改为 fulfilled,括号内参数为异步成功的返回数据,会传递给后续
.then()方法。 -
reject():将 Promise 状态从 pending 改为 rejected,括号内参数为异步错误信息,会传递给后续
.catch()方法。
二、Promise 基础语法拆解(零基础必学)
本节提供标准、完整的 Promise 基础模板,代码可直接在浏览器控制台、Node.js 环境运行,同时逐行拆解语法逻辑,彻底理解执行机制。
2.1 基础可运行代码
// 1. 创建 Promise 异步实例
const promise = new Promise((resolve, reject) => {
// 此处为异步操作执行区:定时器、接口请求、文件读写等
setTimeout(() => {
// 主动触发成功状态,传递结果数据
resolve("异步操作执行成功");
// 主动触发失败状态,传递错误信息(注释打开即可测试失败场景)
// reject("异步操作执行失败:任务超时异常");
}, 1000);
});
// 2. 状态回调处理:成功走then,失败走catch
promise
.then((result) => {
// result 接收 resolve 传递的成功数据
console.log("成功结果:", result);
})
.catch((error) => {
// error 接收 reject 传递的错误信息
console.log("错误信息:", error);
});
2.2 逐行语法解析
-
new Promise():初始化 Promise 实例,括号内的执行器函数会同步立即执行,无需等待。 -
执行器函数内部:专门编写所有异步业务逻辑,是 Promise 的核心执行区域。
-
setTimeout:模拟真实业务中的异步耗时操作。 -
resolve(数据):终止等待状态,锁定为成功态,向外抛出业务数据。 -
reject(错误信息):终止等待状态,锁定为失败态,向外抛出异常信息。 -
.then():专属成功回调,仅在状态变为 fulfilled 时执行,唯一参数为异步返回数据。 -
.catch():专属异常回调,仅在状态变为 rejected 或代码报错时执行,用于统一捕获错误。
三、链式调用:彻底解决回调地狱
3.1 传统回调地狱弊端演示
当多个异步任务需要按顺序串行执行时,传统回调函数会层层嵌套,代码呈金字塔结构,可读性、可维护性极低:
// 多层回调嵌套:回调地狱示例
setTimeout(() => {
console.log("任务1执行完成");
setTimeout(() => {
console.log("任务2执行完成");
setTimeout(() => {
console.log("任务3执行完成");
}, 1000);
}, 1000);
}, 1000);
上述代码层级嵌套严重,新增、删除任务都需要修改嵌套结构,极易产生 Bug,无法用于正式项目开发。
3.2 Promise 链式调用原理与实现
核心原理:Promise 的 .then() 方法执行后,会默认返回一个全新的 Promise 实例,因此可以持续调用 .then(),形成链式结构,彻底扁平化代码。
// 封装通用延时异步方法:统一返回Promise实例
function delay(time) {
return new Promise((resolve) => {
setTimeout(() => resolve(), time);
});
}
// 链式串行执行异步任务
delay(1000)
.then(() => {
console.log("任务1执行完成");
// 返回新的Promise,衔接下一次链式调用
return delay(1000);
})
.then(() => {
console.log("任务2执行完成");
return delay(1000);
})
.then(() => {
console.log("任务3执行完成");
});
优化后代码层级统一、执行顺序清晰,任务独立拆分,支持随意增删,完全适配工程化开发。
四、企业级实战:Promise 封装网络请求
网络请求是 Promise 最核心的落地场景。原生 fetch 接口逻辑零散,通过 Promise 封装后,可统一处理请求成功、状态码异常、网络错误等所有场景,适配正式项目。
4.1 通用请求封装代码(可直接复用)
// 封装通用延时异步方法:统一返回Promise实例
function delay(time) {
return new Promise((resolve) => {
setTimeout(() => resolve(), time);
});
}
// 链式串行执行异步任务
delay(1000)
.then(() => {
console.log("任务1执行完成");
// 返回新的Promise,衔接下一次链式调用
return delay(1000);
})
.then(() => {
console.log("任务2执行完成");
return delay(1000);
})
.then(() => {
console.log("任务3执行完成");
});
4.2 封装优势说明
-
统一拦截 网络失败、接口404、500服务异常、数据解析失败 所有错误场景
-
代码可全局复用,无需每次请求重复编写判断逻辑
-
成功、失败逻辑分离,代码结构清晰,便于统一做弹窗提示、日志打印
五、Promise 高频高阶静态方法
Promise 内置两个核心静态方法,用于批量处理多个异步任务,是项目开发、面试高频考点,适配多接口并行、请求超时拦截场景。
5.1 Promise.all():并行执行,全员成功才返回
核心逻辑:接收一个 Promise 数组,等待所有异步任务执行成功后,统一返回所有结果;任意一个任务失败,直接触发 reject,终止所有执行。
适用场景:页面需要同时请求多个接口,全部加载完成后再渲染页面。
function delay(time) {
return new Promise((resolve) => {
setTimeout(() => resolve(`${time}ms 任务执行完成`), time);
});
}
// 并行执行多个异步任务
Promise.all([delay(1000), delay(2000), delay(1500)])
.then((resultList) => {
// 结果为数组,顺序与传入的任务顺序一致
console.log("所有任务执行完毕:", resultList);
})
.catch((err) => {
console.log("存在任务执行失败:", err);
});
5.2 Promise.race():竞速执行,取首个完成结果
核心逻辑:接收 Promise 数组,多个任务竞速执行,首个完成(成功/失败)的任务直接返回结果,忽略剩余未完成任务。
适用场景:接口请求超时拦截、多渠道数据优先加载。
// 业务请求任务
const requestTask = httpRequest("https://jsonplaceholder.typicode.com/todos/1");
// 超时拦截任务:3秒未完成则判定超时
const timeoutTask = new Promise((_, reject) => {
setTimeout(() => reject("请求超时,请检查网络重试"), 3000);
});
// 竞速判断:请求成功则返回数据,超时则拦截报错
Promise.race([requestTask, timeoutTask])
.then((res) => console.log("请求成功:", res))
.catch((err) => console.log("请求异常:", err));
六、Promise 终极语法糖:async/await
async/await 是 ES7 推出的 Promise 语法糖,本质完全基于 Promise 封装,作用是将异步链式代码转化为同步写法,大幅提升代码可读性,是目前企业开发的主流标准。
6.1 核心使用规则
-
所有异步函数必须添加
async关键字修饰 -
await只能在async函数内部使用,作用是阻塞代码,等待 Promise 执行完成 -
通过
try/catch捕获所有异步异常,替代.catch()方法
6.2 实战代码与解析
function delay(time) {
return new Promise((resolve) => {
setTimeout(() => resolve(`${time}ms 任务完成`), time);
});
}
// async修饰异步函数
async function handleAsyncTask() {
// 通过try包裹正常执行逻辑
try {
// 阻塞等待第一个任务完成
const res1 = await delay(1000);
console.log(res1);
// 阻塞等待第二个任务完成,串行执行
const res2 = await delay(2000);
console.log(res2);
} catch (error) {
// 统一捕获所有任务的异常、报错
console.log("异步任务执行异常:", error);
}
}
// 调用函数
handleAsyncTask();
该写法无链式嵌套、逻辑线性清晰,完全贴合同步代码阅读习惯,是日常开发首选方案。
七、核心知识点总结
-
Promise 是 ES6 标准化异步方案,核心解决回调地狱、代码混乱、异常难处理问题。
-
包含三种不可逆状态:pending(等待)、fulfilled(成功)、rejected(失败),通过 resolve、reject 切换状态。
-
.then()处理成功逻辑,.catch()处理异常逻辑,支持无限链式调用。 -
Promise.all()适配多任务并行场景,全员成功才返回结果。 -
Promise.race()适配竞速场景,常用于接口超时拦截。 -
async/await是 Promise 语法糖,实现异步代码同步化,工程化开发首选。
更多推荐
所有评论(0)