news 2026/4/16 20:05:15

【前端知识点总结】Promise的介绍

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【前端知识点总结】Promise的介绍

1. Promise 是什么?

想象一下,你去一家网红奶茶店买奶茶。因为人太多,店员不能立刻做好。这时你有两个选择:

选择一:一直等(同步思维):

你就站在柜台前,眼睛死死盯着制作区,什么也不干,直到奶茶做好。这期间你不能玩手机、不能和朋友聊天,时间被完全浪费。这就是同步,一件事做不完,下一件就别想开始。

选择二:拿个小票(异步思维):

店员给了你一张小票,说:“好了会叫你。” 现在你自由了!你可以去玩手机、聊天,等叫号了再过去取。这张小票,就是Promise

Promise就是一张“未来会得到结果的承诺”小票。

它有三种状态:

  1. 进行中:你拿着小票,等待中。这是 Promise 的初始状态。
  2. 已成功:店员喊你的号,你成功拿到了一杯美味的奶茶。这个承诺兑现了。
  3. 已失败:店员过来说:“不好意思,您点的珍珠卖完了。” 这个承诺失败了。

关键点:一个 Promise 一旦从 pending 变为 fulfilled 或 rejected,它的状态就永远确定了,不能再改变。就像你拿到了奶茶(成功)或被告知卖完了(失败),这件事就结束了。

2. Promise 怎么用?—— .then() 和 .catch()

我们拿到了“小票”(Promise),接下来该怎么处理结果呢?Promise 提供了两个核心方法:.then() 和 .catch()。

  • .then():相当于你对店员说:“如果成功了,请给我加冰。”
  • .catch():相当于你对店员说:“如果失败了,请帮我退钱。”

代码示例:

假设我们有一个函数 buyMilkTea(),它会立即返回一个“小票”(Promise),而不是奶茶本身。

// 这个函数模拟买奶茶的过程,2秒后返回结果 function buyMilkTea() { return new Promise((resolve, reject) => { console.log('👨‍🍳 店员开始制作奶茶...'); setTimeout(() => { if (Math.random() > 0.2) { // 80% 的概率成功 resolve('🥤 一杯美味的珍珠奶茶'); // 兑现承诺 } else { // 20% 的概率失败 reject('😢 很抱歉,珍珠卖完了'); // 承诺失败 } }, 2000); // 模拟2秒的制作时间 }); } // 开始买奶茶 const milkTeaPromise = buyMilkTea(); // 拿到小票后,我们告诉店员成功和失败分别要怎么做 milkTeaPromise .then(milkTea => { // 如果 resolve 被调用,这里的代码就会执行 // milkTea 就是 resolve 传过来的值 console.log('成功拿到:', milkTea); console.log('开心地喝起来!'); }) .catch(error => { // 如果 reject 被调用,这里的代码就会执行 // error 就是 reject 传过来的值 console.log('出错了:', error); console.log('只好去买瓶水了。'); });

运行结果可能是:

👨‍🍳 店员开始制作奶茶... (2秒后) 成功拿到: 🥤 一杯美味的珍珠奶茶 开心地喝起来!

也可能是:

👨‍🍳 店员开始制作奶茶... (2秒后) 出错了: 😢 很抱歉,珍珠卖完了 只好去买瓶水了。

这就是 Promise 的基本用法:它让你能够优雅地处理一个未来才会知道结果的任务,而不需要阻塞代码的执行。

3. 为什么企业项目离不开 Promise?

在企业级开发中,我们无时无刻不在处理异步操作,例如:

  • 发送 API 请求获取数据。
  • 读写浏览器本地存储。
  • 用户操作确认(如“您确定要删除吗?”的弹窗)。

Promise 是处理这些异步操作的“官方标准”,它解决了传统回调函数带来的“回调地狱”问题。

什么是回调地狱?

想象一下,你要依次做三件事:1. 登录获取用户ID -> 2. 用ID获取用户详情 -> 3. 用详情里的订单ID获取订单列表。

如果用传统的回调函数,代码会是这样:

login((loginRes) => { getUserInfo(loginRes.userId, (userRes) => { getOrderList(userRes.orderId, (orderRes) => { console.log('订单列表:', orderRes); // 如果还有第四步、第五步...代码会不断向右缩进,形成金字塔 }); }); });

这种层层嵌套、难以阅读和维护的代码,就是“回调地狱”。

用 Promise 链式调用拯救它:

如果上面三个函数都返回 Promise,代码会变得非常清晰:

login() .then(loginRes => getUserInfo(loginRes.userId)) .then(userRes => getOrderList(userRes.orderId)) .then(orderRes => { console.log('订单列表:', orderRes); }) .catch(error => { // 任何一步出错,都会在这里被捕获 console.error('出错了:', error); });

代码像一条平缓的河流,从上到下流动,逻辑清晰,错误处理也集中在一处。这就是 Promise 在企业开发中的核心价值:让异步代码的编写和阅读体验,无限接近于同步代码。

4. Promise 的写法

new Promise(executor):

Promise 的构造函数非常简洁,它只接受一个参数

const promise = new Promise(executor);

executor 是一个函数,它会在 new Promise() 创建实例时被立即同步执行。这个函数本身又接收两个参数,它们都是函数,由 JavaScript 引擎提供,我们称之为“回调函数”。

const executor = (resolve, reject) => { // 异步操作的逻辑写在这里 // ... }; const promise = new Promise(executor);

第一个参数:resolve 函数

作用:将 Promise 的状态从 pending(进行中)变为 fulfilled(已成功)。

何时调用:当你的异步操作成功完成时,你应该调用 resolve。

参数:resolve 可以接收一个参数,这个参数将成为 Promise 成功后,传递给.then()回调函数的值。

// 专业写法示例:成功时调用 resolve function fetchUserData(userId) { return new Promise((resolve, reject) => { console.log('开始获取用户数据...'); // 模拟一个异步请求,比如使用 fetch 或 axios setTimeout(() => { // 假设我们成功从服务器获取到了数据 const userData = { id: userId, name: '张三', role: 'admin' }; // 调用 resolve,并将数据作为参数传递出去 resolve(userData); // <-- 关键点 }, 1000); }); } // 使用 fetchUserData(123).then(data => { // 这里的 data 就是 resolve(userData) 传过来的值 console.log('成功获取到数据:', data); // { id: 123, name: '张三', role: 'admin' } });

专业技巧:resolve 的参数可以是任何合法的 JavaScript 值,包括另一个 Promise。如果传入的是另一个 Promise,那么当前 Promise 的状态将完全由传入的 Promise 决定。

第二个参数:reject 函数

作用:将 Promise 的状态从 pending(进行中)变为 rejected(已失败)。

何时调用:当你的异步操作失败或遇到错误时,你应该调用 reject。

参数:reject 通常接收一个参数,这个参数会成为 Promise 失败的原因,并传递给 .catch() 回调函数。强烈建议传递一个 Error 对象,因为它能携带错误堆栈信息,极大地方便调试。

// 专业写法示例:失败时调用 reject function fetchUserData(userId) { return new Promise((resolve, reject) => { console.log('开始获取用户数据...'); setTimeout(() => { if (userId > 100) { // 模拟一个错误:用户不存在 // 专业做法:创建一个 Error 对象 const error = new Error(`用户ID ${userId} 不存在`); // 调用 reject,并将错误对象作为参数传递出去 reject(error); // <-- 关键点 } else { const userData = { id: userId, name: '张三', role: 'admin' }; resolve(userData); } }, 1000); }); } // 使用 fetchUserData(999) .then(data => { console.log('成功获取到数据:', data); }) .catch(err => { // 这里的 err 就是 reject(new Error(...)) 传过来的值 console.error('获取数据失败:', err.message); // "获取数据失败: 用户ID 999 不存在" console.error('错误堆栈:', err.stack); // 可以看到详细的错误调用栈 });

为什么用 Error 对象?

如果你只传一个字符串 reject('用户不存在'),在 .catch 里你只能拿到这个字符串。而用 new Error(),你不仅能拿到 message,还能拿到 stack(错误堆栈),这对于定位问题根源至关重要,是企业级项目开发的规范。

总结与最佳实践

1. executor 的错误处理

executor 函数内部如果发生同步错误(比如 throw new Error()),Promise 会自动捕获并将其状态变为 rejected,效果等同于调用 reject。

new Promise((resolve, reject) => { // 下面这行代码会抛出错误 const data = JSON.parse('这不是一个合法的JSON字符串'); }).catch(err => { console.log(err.message); // "Unexpected token..." });

虽然Promise会自动捕获,但为了代码的清晰和可控,更推荐显式地使用try...catch包裹你的逻辑。

function doSomethingRisky() { return new Promise((resolve, reject) => { try { // 可能出错的同步代码 const result = someRiskySyncOperation(); resolve(result); } catch (syncError) { // 同步错误,直接 reject reject(syncError); } // 异步操作(如回调、setTimeout)的错误需要在回调内部处理 someAsyncOperation((err, asyncResult) => { if (err) { // 异步错误,也 reject reject(err); } else { resolve(asyncResult); } }); }); }

2. 现代开发中,其实很少直接 new Promise

在企业项目中,绝大多数时候不会亲手去写 new Promise(...)。因为:

  • 现代 Web API:fetch、Service Worker 等原生 API 已经返回 Promise 对象。
  • 第三方库:axios、request 等库的请求方法也返回 Promise。
  • 工具函数:很多工具库已经将回调式的 API 封装成了 Promise。

更多的时候是作为 Promise 的消费者,使用.then(),.catch(),.async/await

那你什么时候需要自己写 new Promise?

当你需要将一个非 Promise 的异步操作(通常是旧式的回调函数)封装成 Promise 时。这被称为“Promise化”(Promisification)。

经典案例:封装 setTimeout

// 封装一个延时函数 function delay(ms) { return new Promise(resolve => { setTimeout(resolve, ms); // ms 毫秒后,调用 resolve() }); } // 使用 console.log('开始'); delay(2000).then(() => { console.log('2秒后执行'); // 2秒后打印 });

经典案例:封装一个回调式的文件读取 API(Node.js 环境)

const fs = require('fs'); // 原始回调式 API // fs.readFile('file.txt', 'utf8', (err, data) => { ... }); // Promise化封装 function readFilePromise(path, encoding) { return new Promise((resolve, reject) => { fs.readFile(path, encoding, (err, data) => { if (err) { // 如果有错误,调用 reject reject(err); } else { // 如果成功,调用 resolve resolve(data); } }); }); } // 使用 readFilePromise('./myfile.txt', 'utf8') .then(content => { console.log('文件内容:', content); }) .catch(err => { console.error('读取文件失败:', err); });

5. 企业级项目中的 Promise 实战技巧

在企业项目中,我们不仅要会用 Promise,更要用好它。

技巧一:统一封装 API 请求

场景:企业项目中,所有API 请求都应该返回Promise,这样上层业务逻辑才能使用.then().catch()

我们配置的 axios 拦截器,其实就是在做这件事。axios.get() 或 axios.post() 本身返回的就是一个 Promise 对象。

// src/api/user.js import request from '@/utils/request'; // 这个 request 就是一个封装好的 axios 实例 // 这个函数返回一个 Promise export function getUserInfo(userId) { return request.get(`/users/${userId}`); } // 在组件中使用 getUserInfo(123) .then(data => { this.userInfo = data; }) .catch(error => { this.$message.error(error.message); });

技巧二:使用 async/await 语法糖

async/await是建立在Promise之上的语法糖,它让我们能用写同步代码的方式来写异步代码,是现代前端开发的标配。

规则:

  1. 在一个函数前加上 async 关键字,这个函数就变成了异步函数。
  2. 在异步函数里,可以用 await 关键字来“等待”一个 Promise 完成。
  3. await 只能在 async 函数内部使用。

把上面的Promise 链式调用改写成async/await

// 定义一个 async 函数 async function fetchOrderList() { try { // 用 await 等待 Promise 成功,并把结果赋值给变量 const loginRes = await login(); const userRes = await getUserInfo(loginRes.userId); const orderRes = await getOrderList(userRes.orderId); console.log('订单列表:', orderRes); } catch (error) { // 任何一步出错,都会被 catch 捕获 console.error('出错了:', error); } } fetchOrderList();


技巧三:并发处理多个请求

场景:页面需要同时展示用户信息和用户权限,这两个信息来自不同的 API 接口,并且它们之间没有依赖关系。

错误做法:串行请求,浪费时间。

// 总耗时 = 时间A + 时间B const userInfo = await getUserInfo(); const userPermissions = await getUserPermissions();

正确做法:使用Promise.all()并发请求。

Promise.all() 接收一个 Promise 数组,当所有 Promise 都成功时,它才会成功,并返回一个包含所有结果的数组。只要有一个失败,它就立即失败。

async function loadUserData() { try { // 同时发起两个请求,总耗时 = max(时间A, 时间B) const [userInfo, userPermissions] = await Promise.all([ getUserInfo(), getUserPermissions() ]); console.log('用户信息:', userInfo); console.log('用户权限:', userPermissions); } catch (error) { console.error('加载用户数据失败:', error); } } loadUserData();

这在企业项目中是提升页面加载性能的常用技巧。

技巧四:处理竞态条件

场景:用户快速点击一个按钮,会连续发起多个相同的请求。我们只关心最后一次请求的结果,之前的都应该被忽略。

解决思路:在发送新请求前,取消掉上一个未完成的请求。这在axios中可以通过CancelToken或 AbortController实现,通常也会封装在请求拦截器里。

let pendingRequest = null; async function fetchData(id) { // 如果上一个请求还在,就取消它 if (pendingRequest) { pendingRequest.cancel('取消上一个请求'); } // 创建新的取消令牌 const CancelToken = axios.CancelToken; pendingRequest = CancelToken.source(); try { const response = await axios.get(`/api/data/${id}`, { cancelToken: pendingRequest.token }); console.log(response.data); pendingRequest = null; // 请求完成后,清除引用 } catch (error) { if (axios.isCancel(error)) { console.log(error.message); // "取消上一个请求" } else { console.error('请求失败:', error); } } }


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 9:59:32

2025-12-19:包含 K 个连通分量需要的最小时间。用go语言,给定一个含有 n 个顶点(编号 0 到 n-1)的无向图,图中的每条边用三元组 edges[i] = [ui, vi, timei

2025-12-19&#xff1a;包含 K 个连通分量需要的最小时间。用go语言&#xff0c;给定一个含有 n 个顶点&#xff08;编号 0 到 n-1&#xff09;的无向图&#xff0c;图中的每条边用三元组 edges[i] [ui, vi, timei] 表示&#xff0c;含义是该无向边连接 ui 和 vi&#xff0c;并…

作者头像 李华
网站建设 2026/4/16 12:52:04

Flutter 权限管理实战手册:permission_handler 全平台适配与最佳实践

【前言】在 Flutter 跨平台开发中&#xff0c;访问设备核心功能&#xff08;如相机、定位、相册、麦克风等&#xff09;均需获取用户授权。permission_handler 作为 Flutter 生态中最主流的权限管理插件&#xff0c;提供了统一的跨平台 API&#xff0c;支持 iOS、Android 等系统…

作者头像 李华
网站建设 2026/4/16 11:11:17

21、正则表达式完全指南

正则表达式完全指南 正则表达式基础 正则表达式是用于匹配文本模式的强大工具。它由普通字符和元字符组成。普通字符就是它们本身,而元字符则用于指定更复杂的匹配规则。正则表达式的元字符包括: ^ $ . [ ] { } - ? * + ( ) | \ 。除了这些元字符,其他字符都被视为普通…

作者头像 李华
网站建设 2026/4/16 11:15:29

深入浅出 Ajax:从原理到实战,打造高可靠的前端请求方案

在前端开发中&#xff0c;Ajax&#xff08;Asynchronous JavaScript and XML&#xff09;是实现 “无刷新交互” 的核心技术&#xff0c;尤其在后台管理系统、电商平台等场景中&#xff0c;Ajax 几乎是前端与后端通信的标配。尽管如今 Fetch API、Axios 等工具层出不穷&#xf…

作者头像 李华
网站建设 2026/4/16 13:00:30

语言模型中的因果推理能力增强方法

语言模型中的因果推理能力增强方法关键词&#xff1a;语言模型、因果推理能力、增强方法、算法原理、应用场景摘要&#xff1a;本文围绕语言模型中的因果推理能力增强方法展开深入探讨。首先介绍了研究背景&#xff0c;包括目的范围、预期读者等内容。接着阐述了因果推理与语言…

作者头像 李华