前端老铁别再混用 Promises 和 Observables 了!搞懂区别少踩80%
- 前端老铁别再混用 Promises 和 Observables 了!搞懂区别少踩80%的坑
- Promise 是啥?——"一次性暖男"
- Observable 是啥?——"海后级多面手"
- 把他俩拖进同一个场景,真刀真枪比一比
- 场景 1:搜索框防抖 ——Promise 瞬间自闭
- Promise 版(手搓 setTimeout,回调地狱预定):
- Observable 版(一行操作符搞定):
- 场景 2:文件上传带进度条 ——Promise 直接抓瞎
- Promise 版(fetch 根本拿不到 progress 事件):
- Observable 版(XMLHttpRequest 套壳,progress 稳稳到手):
- 场景 3:WebSocket 实时聊天 ——Promise 连门都进不去
- Promise 版(直接无解,Promise 只能拿一次值,流式数据天生绝缘):
- Observable 版(封装 WebSocket,消息流丝滑切换):
- 把 Observable 当 Promise 用?——from 和 lastValueFrom 了解一下
- 1. Promise → Observable(from 一行解决)
- 2. Observable → Promise(lastValueFrom,Angular 13+ 官方推荐)
- 内存泄漏大坑:unsubscribe 不写,老板请喝茶
- 正确姿势 1:手动 unsubscribe
- 正确姿势 2:async 管道(Angular 推荐,零泄漏)
- 性能对比:数据说话,别瞎吹
- 私藏技巧大放送,拿走不谢
- 什么时候该选谁?一张草图秒懂
- 尾声:别盲目崇拜,也别无脑黑
前端老铁别再混用 Promises 和 Observables 了!搞懂区别少踩80%的坑
友情提示:本文全程碎碎念,代码管够,吐槽管饱,阅读时建议配一杯冰美式,防止打瞌睡。
Promise 是啥?——“一次性暖男”
先给 Promise 发张好人卡:它就像那种只暖你一次的暖男,说出去的话泼出去的水,一旦 resolve 就彻底锁死,想反悔?门都没有。
最经典的打开方式:
// 1. 原生 fetch,返回 Promisefetch('/api/user').then(res=>res.json()).then(data=>console.log('用户',data)).catch(err=>console.error('凉了',err));看起来人畜无害,对吧?但一旦场景升级,暖男瞬间变"猪队友":
- 用户狂点搜索按钮,Promise 管你点几次,全部请求照单全收,回来顺序还给你乱成一锅粥;
- WebSocket 推股票行情,Promise 只能拿一次数据,想持续监听?对不起,臣妾做不到;
- 中途想取消请求?不好意思,Promise 没长腿,跑不了也死不掉。
总结一句:Promise 就是"一次交付,终身负责",简单场景够用,复杂场景直接原地爆炸。
Observable 是啥?——“海后级多面手”
如果 Promise 是暖男,那 Observable 就是海后,备胎成群、操作拉满,还能随时拉黑(unsubscribe):
// 1. 先整一个能发多次的数据流import{interval}from'rxjs';interval(1000)// 每秒吐一个数字.subscribe(n=>console.log('第',n,'秒'));看到没?数据可以一直流,你只要不喊停,她就不停;想甩了?一句话:
constsub=interval(1000).subscribe(...);sub.unsubscribe();// 拉黑,江湖再见而且海后技能树深不见底:map、filter、merge、switchMap、debounceTime……花式操作多到你想不到。后面实战挨个给你拆解,别急。
把他俩拖进同一个场景,真刀真枪比一比
场景 1:搜索框防抖 ——Promise 瞬间自闭
需求:用户输入停 300ms 再发请求,减少 QPS。
Promise 版(手搓 setTimeout,回调地狱预定):
lettimer=null;functionsearch(keyword){clearTimeout(timer);timer=setTimeout(async()=>{constres=awaitfetch(`/api/search?q=${keyword}`);constdata=awaitres.json();console.log('结果',data);},300);}写起来就一股 jQuery 时代的味道,而且如果用户输入太快,旧请求仍可能后返回,结果顺序错乱,体验拉垮。
Observable 版(一行操作符搞定):
import{fromEvent}from'rxjs';import{debounceTime,distinctUntilChanged,switchMap}from'rxjs/operators';fromEvent(inputDom,'input').pipe(debounceTime(300),// 防抖 300msdistinctUntilChanged(),// 内容没变就跳过switchMap(e=>fetch(`/api/search?q=${e.target.value}`))// 新请求直接顶掉旧请求).subscribe(res=>console.log('结果',res));switchMap会自动取消上次未返回的请求,保证最新输入对应最新结果,这波操作 Promise 只能原地流泪。
场景 2:文件上传带进度条 ——Promise 直接抓瞎
需求:展示实时上传百分比。
Promise 版(fetch 根本拿不到 progress 事件):
// fetch 不支持 onprogress,只能干等结束,进度条随缘awaitfetch('/upload',{method:'POST',body:file});console.log('上传完了,进度条全靠 P 图');Observable 版(XMLHttpRequest 套壳,progress 稳稳到手):
import{Observable}from'rxjs';functionupload(file){returnnewObservable(observer=>{constxhr=newXMLHttpRequest();xhr.open('POST','/upload');// 进度事件xhr.upload.onprogress=(e)=>{if(e.lengthComputable){constpercent=Math.round(e.loaded/e.total*100);observer.next(percent);// 实时把百分比推出去}};xhr.onload=()=>observer.complete();xhr.onerror=err=>observer.error(err);xhr.send(file);});}// 使用upload(file).subscribe(p=>(progressBar.style.width=p+'%'));实时百分比推送,Promise 只能望洋兴叹。
场景 3:WebSocket 实时聊天 ——Promise 连门都进不去
需求:接收持续的消息流。
Promise 版(直接无解,Promise 只能拿一次值,流式数据天生绝缘):
// Promise 无法处理持续推送,只能告辞Observable 版(封装 WebSocket,消息流丝滑切换):
import{webSocket}from'rxjs/webSocket';constws$=webSocket('wss://echo.websocket.org');// 发消息ws$.next({msg:'老铁在吗?'});// 收消息ws$.subscribe(msg=>console.log('收到',msg),err=>console.error('出错',err),()=>console.log('连接关了'));消息想发就发,想收就收,背压控制、重连机制都能用 operators 继续堆,Promise 只能原地解散。
把 Observable 当 Promise 用?——from 和 lastValueFrom 了解一下
有时候你只想快速把现有 Promise 代码迁移成流,或者反过来把流伪装成 Promise 给老模块调用,RxJS 给你两条捷径:
1. Promise → Observable(from 一行解决)
import{from}from'rxjs';from(fetch('/api/user'))// 把 Promise 包成 Observable.subscribe(res=>console.log(res));2. Observable → Promise(lastValueFrom,Angular 13+ 官方推荐)
import{lastValueFrom}from'rxjs';// 假设 userService 返回的是 Observableconstusers=awaitlastValueFrom(userService.getUsers$());console.log('用户列表',users);注意,老版本里的toPromise()已被废弃,新项目别再用,免得以后升级哭瞎。
内存泄漏大坑:unsubscribe 不写,老板请喝茶
Observable 功能多,坑位也多,最臭名昭著的就是内存泄漏:
ngOnInit(){this.userService.getUsers$().subscribe(users=>this.users=users);// 没取消订阅 → 组件销毁后回调还在 → 内存飙升 → 页面越用越卡}正确姿势 1:手动 unsubscribe
privatesub=newSubscription();ngOnInit(){this.sub.add(this.userService.getUsers$().subscribe(users=>this.users=users));}ngOnDestroy(){this.sub.unsubscribe();// 统一释放}正确姿势 2:async 管道(Angular 推荐,零泄漏)
// 组件里users$=this.userService.getUsers$();// 模板里<div*ngFor="let u of users$ | async">{{u.name}}</div>模板自动帮你订阅 + 销毁,手残党福音。
性能对比:数据说话,别瞎吹
在 ABP 框架里有人跑过实际基准 :
| 场景 | Promise 耗时 | Observable 耗时 | 备注 |
|---|---|---|---|
| 单次 HTTP | 120ms | 125ms | 基本打平 |
| 高频更新 | 不支持 | 5ms/次 | Observable 完胜 |
| 内存占用 | 低 | 稍高 | 操作符开销 |
| 取消操作 | 不支持 | 即时释放 | Observable 完胜 |
结论:单次请求两者一样快,但流式、可取消、高频场景 Observable 碾压;包体积多出的十几 KB,换这些特性不亏。
私藏技巧大放送,拿走不谢
- switchMap 防抖动请求:比 AbortController 优雅,旧请求自动取消;
- debounceTime + distinctUntilChanged:搜索框标配,300ms 防抖 + 相同输入跳过;
- retry + catchError:网络抖动自动重试,失败还能降级到本地缓存;
- 按需引入操作符:
import { map } from 'rxjs/operators'一句省几十 KB,别import *一把梭; - 调试想打 log:
tap(val => console.log('路过', val)),无副作用,调试神器。
什么时候该选谁?一张草图秒懂
| 场景 | 用 Promise | 用 Observable |
|---|---|---|
| 一次性 HTTP | ✅ 简单直接 | 也可以,但杀鸡用牛刀 |
| 持续事件流(WebSocket、输入) | ❌ 不支持 | ✅ 天生为此而生 |
| 需要取消/重试 | ❌ 做不到 | ✅ operators 安排 |
| 包体积敏感(营销落地页) | ✅ 原生无依赖 | ❌ 需引 RxJS |
| 团队新手多,排期紧 | ✅ 人人会 | ❌ 学习成本 |
一句话总结:简单一次性 Promise 足够,流式复杂 Observable 真香。
尾声:别盲目崇拜,也别无脑黑
Promise 就像老牌瑞士军刀,随手能用;Observable 更像电动工具箱,功能炸裂但要读说明书。两者不是对立,而是互补——知道差异、选对场景、少踩 80% 的坑,才是本文最想给你的忠告。
下次leader再让你"全都改成 Observable",你可以甩他这篇文章;同事再嘲笑"Promise 老掉牙",你也可以把防抖搜索的代码糊他脸上。技术选型,适合最好,能 hold 住最妙。
- 代码写完了,咖啡也凉了,去重构吧,老铁!
- https://blog.csdn.net/gitblog_00944/article/details/148392188
欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
推荐:DTcode7的博客首页。
一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身农奴把歌唱,一边打入敌人内部一边持续提升自己,为我们广大开发同胞谋福祉,坚决抵制睿智产品折磨我们码农兄弟!
| 专栏系列(点击解锁) | 学习路线(点击解锁) | 知识定位 |
|---|---|---|
| 《微信小程序相关博客》 | 持续更新中~ | 结合微信官方原生框架、uniapp等小程序框架,记录请求、封装、tabbar、UI组件的学习记录和使用技巧等 |
| 《AIGC相关博客》 | 持续更新中~ | AIGC、AI生产力工具的介绍,例如stable diffusion这种的AI绘画工具安装、使用、技巧等总结 |
| 《HTML网站开发相关》 | 《前端基础入门三大核心之html相关博客》 | 前端基础入门三大核心之html板块的内容,入坑前端或者辅助学习的必看知识 |
| 《前端基础入门三大核心之JS相关博客》 | 前端JS是JavaScript语言在网页开发中的应用,负责实现交互效果和动态内容。它与HTML和CSS并称前端三剑客,共同构建用户界面。 通过操作DOM元素、响应事件、发起网络请求等,JS使页面能够响应用户行为,实现数据动态展示和页面流畅跳转,是现代Web开发的核心 | |
| 《前端基础入门三大核心之CSS相关博客》 | 介绍前端开发中遇到的CSS疑问和各种奇妙的CSS语法,同时收集精美的CSS效果代码,用来丰富你的web网页 | |
| 《canvas绘图相关博客》 | Canvas是HTML5中用于绘制图形的元素,通过JavaScript及其提供的绘图API,开发者可以在网页上绘制出各种复杂的图形、动画和图像效果。Canvas提供了高度的灵活性和控制力,使得前端绘图技术更加丰富和多样化 | |
| 《Vue实战相关博客》 | 持续更新中~ | 详细总结了常用UI库elementUI的使用技巧以及Vue的学习之旅 |
| 《python相关博客》 | 持续更新中~ | Python,简洁易学的编程语言,强大到足以应对各种应用场景,是编程新手的理想选择,也是专业人士的得力工具 |
| 《sql数据库相关博客》 | 持续更新中~ | SQL数据库:高效管理数据的利器,学会SQL,轻松驾驭结构化数据,解锁数据分析与挖掘的无限可能 |
| 《算法系列相关博客》 | 持续更新中~ | 算法与数据结构学习总结,通过JS来编写处理复杂有趣的算法问题,提升你的技术思维 |
| 《IT信息技术相关博客》 | 持续更新中~ | 作为信息化人员所需要掌握的底层技术,涉及软件开发、网络建设、系统维护等领域的知识 |
| 《信息化人员基础技能知识相关博客》 | 无论你是开发、产品、实施、经理,只要是从事信息化相关行业的人员,都应该掌握这些信息化的基础知识,可以不精通但是一定要了解,避免日常工作中贻笑大方 | |
| 《信息化技能面试宝典相关博客》 | 涉及信息化相关工作基础知识和面试技巧,提升自我能力与面试通过率,扩展知识面 | |
| 《前端开发习惯与小技巧相关博客》 | 持续更新中~ | 罗列常用的开发工具使用技巧,如 Vscode快捷键操作、Git、CMD、游览器控制台等 |
| 《photoshop相关博客》 | 持续更新中~ | 基础的PS学习记录,含括PPI与DPI、物理像素dp、逻辑像素dip、矢量图和位图以及帧动画等的学习总结 |
| 日常开发&办公&生产【实用工具】分享相关博客》 | 持续更新中~ | 分享介绍各种开发中、工作中、个人生产以及学习上的工具,丰富阅历,给大家提供处理事情的更多角度,学习了解更多的便利工具,如Fiddler抓包、办公快捷键、虚拟机VMware等工具 |
吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!