当前位置: 首页 > news >正文

23-异步编程

异步编程:回调函数与 Promise

JavaScript 是单线程的,但可以通过异步编程处理耗时任务而不阻塞主线程。从回调函数到 Promise,异步代码的写法发生了革命性变化。


学习目标

读完本文,你将学会:

  • 同步与异步的区别,为什么需要异步
  • 回调函数的工作原理与回调地狱问题
  • Promise 的基本用法:resolve、reject、then、catch、finally
  • Promise 链式调用与错误处理
  • Promise.all / Promise.race / Promise.allSettled 的用途
  • 将回调函数包装为 Promise( promisify )

一、为什么需要异步

1.1 单线程的 JavaScript

JavaScript 在浏览器中运行在单线程上,同一时间只能做一件事:

console.log("开始");// 假设这个操作耗时 3 秒for(leti=0;i<1000000000;i++){}console.log("结束");// 在这 3 秒内,页面完全卡死,无法交互

如果网络请求、文件读取等耗时操作也同步执行,用户体验会非常差。

1.2 同步 vs 异步

// 同步:按顺序执行,阻塞后续代码console.log("A");console.log("B");console.log("C");// 输出:A → B → C// 异步:不等待完成,继续执行后续代码console.log("A");setTimeout(()=>console.log("B"),1000);console.log("C");// 输出:A → C → B(1秒后)

二、回调函数

2.1 什么是回调函数

回调函数是作为参数传递给另一个函数的函数,在某个操作完成后被调用:

functionfetchData(callback){setTimeout(()=>{constdata={id:1,name:"小明"};callback(data);// 操作完成后调用回调},1000);}fetchData((data)=>{console.log("收到数据:",data);});

2.2 回调地狱(Callback Hell)

当多个异步操作需要按顺序执行时,回调会一层层嵌套:

getUserData(1,(user)=>{getOrders(user.id,(orders)=>{getProducts(orders[0].id,(products)=>{getStock(products[0].id,(stock)=>{console.log("库存:",stock);});});});});

这种嵌套带来三个问题:

  1. 可读性差:代码向右不断缩进,形成"金字塔"
  2. 错误处理困难:每层都需要写错误处理
  3. 耦合度高:逻辑顺序和代码嵌套结构强绑定

三、Promise:更优雅的异步解决方案

3.1 Promise 是什么

Promise 是 ES6 引入的异步编程解决方案,代表一个尚未完成但预期将来会完成的操作

Promise 有三种状态:

  • pending(等待中):初始状态
  • fulfilled(已成功):操作成功完成
  • rejected(已失败):操作失败

状态一旦改变(从 pending 变为 fulfilled 或 rejected),就不可再次改变。

3.2 创建 Promise

constpromise=newPromise((resolve,reject)=>{// 异步操作setTimeout(()=>{constsuccess=true;if(success){resolve("操作成功!");// pending → fulfilled}else{reject("操作失败!");// pending → rejected}},1000);});

3.3 消费 Promise:then / catch / finally

constpromise=newPromise((resolve,reject)=>{setTimeout(()=>resolve("数据加载完成"),1000);});promise.then((value)=>{console.log("成功:",value);// "成功: 数据加载完成"}).catch((error)=>{console.log("失败:",error);}).finally(()=>{console.log("无论成败都会执行");});

3.4 用 Promise 改写回调地狱

functiongetUserData(id){returnnewPromise((resolve)=>{setTimeout(()=>resolve({id,name:"小明"}),500);});}functiongetOrders(userId){returnnewPromise((resolve)=>{setTimeout(()=>resolve([{id:101,userId}]),500);});}// Promise 链式调用getUserData(1).then((user)=>getOrders(user.id)).then((orders)=>{console.log("订单:",orders);}).catch((err)=>{console.log("出错了:",err);});

相比回调地狱,Promise 链:

  • 代码扁平化,不再向右缩进
  • 统一在末尾用 catch 处理错误
  • 每个 then 返回新的 Promise,可以继续链式调用

四、Promise 进阶

4.1 then 的返回值

then 中的返回值会被包装成新的 Promise:

Promise.resolve(1).then((v)=>v+1)// 返回 2,被包装为 Promise.resolve(2).then((v)=>v+1)// 返回 3.then((v)=>console.log(v));// 3

如果在 then 中返回另一个 Promise,会等待它完成:

Promise.resolve("开始").then((msg)=>{returnnewPromise((resolve)=>{setTimeout(()=>resolve(msg+" → 中间"),500);});}).then((msg)=>console.log(msg));// "开始 → 中间"

4.2 Promise.all:等待全部完成

当多个异步操作互不依赖、需要全部完成后继续时:

constp1=fetch('/api/users');constp2=fetch('/api/products');constp3=fetch('/api/orders');Promise.all([p1,p2,p3]).then(([users,products,orders])=>{console.log("全部加载完成");}).catch((err)=>{console.log("任意一个失败:",err);});
  • 所有 Promise 都成功 → 返回结果数组
  • 任意一个失败→ 立即 reject

4.3 Promise.race:只取最快的结果

constdataPromise=fetch('/api/data').then(r=>r.json());consttimeoutPromise=newPromise((_,reject)=>{setTimeout(()=>reject("请求超时"),5000);});Promise.race([dataPromise,timeoutPromise]).then((data)=>console.log(data)).catch((err)=>console.log(err));

4.4 Promise.allSettled:无论成败都等全部完成

constpromises=[Promise.resolve("成功1"),Promise.reject("失败"),Promise.resolve("成功2")];Promise.allSettled(promises).then((results)=>{console.log(results);// [// { status: "fulfilled", value: "成功1" },// { status: "rejected", reason: "失败" },// { status: "fulfilled", value: "成功2" }// ]});

适合需要知道每个请求的结果、不想因为一个失败就中断的场景。

4.5 Promise.resolve 和 Promise.reject

快速创建已确定状态的 Promise:

Promise.resolve("直接成功").then(v=>console.log(v));Promise.reject("直接失败").catch(e=>console.log(e));// 将非 Promise 值转为 PromisePromise.resolve(42).then(v=>console.log(v));// 42

五、将回调函数转为 Promise

很多旧 API(如 Node.js 的 fs.readFile)使用回调风格,可以包装为 Promise:

constfs=require("fs");// 原始回调风格fs.readFile("file.txt","utf8",(err,data)=>{if(err){console.log("读取失败:",err);}else{console.log("内容:",data);}});// 包装为 PromisefunctionreadFilePromise(path){returnnewPromise((resolve,reject)=>{fs.readFile(path,"utf8",(err,data)=>{if(err)reject(err);elseresolve(data);});});}// 使用readFilePromise("file.txt").then((data)=>console.log(data)).catch((err)=>console.log(err));

六、常见误区与注意点

误区正确理解
Promise 让代码变成多线程Promise 不创建新线程,只是让异步代码组织更优雅
new Promise中的代码是异步的new Promise传入的函数是同步执行的,只有 resolve/reject 后才是异步
then 中不返回值也能继续链式调用不返回相当于返回undefined,后续 then 收到undefined
catch 只捕获前面的错误catch 之后的 then 仍会执行,除非 catch 里又抛错
Promise.all 一个失败全部丢失确实如此,需要 allSettled 来保留所有结果
忘记写 catch 不会报错未捕获的 Promise rejection 可能静默失败,现代浏览器会报警告

new Promise 的执行时机

console.log("A");newPromise((resolve)=>{console.log("B");// 同步执行!resolve();}).then(()=>{console.log("C");// 异步执行});console.log("D");// 输出:A → B → D → C

七、动手练习

练习 1:实现 delay 函数

写一个返回 Promise 的 delay 函数,延迟指定毫秒后 resolve:

delay(1000).then(()=>console.log("1秒后执行"));
参考答案
functiondelay(ms){returnnewPromise((resolve)=>{setTimeout(resolve,ms);});}// 使用delay(1000).then(()=>console.log("1秒后"));

练习 2:按顺序执行 Promise

写一个runInSequence函数,将一组返回 Promise 的函数按顺序执行:

consttasks=[()=>delay(1000).then(()=>"任务1"),()=>delay(500).then(()=>"任务2"),()=>delay(200).then(()=>"任务3")];runInSequence(tasks).then((results)=>{console.log(results);// ["任务1", "任务2", "任务3"]});
参考答案
functionrunInSequence(tasks){constresults=[];returntasks.reduce((promise,task)=>{returnpromise.then(task).then((result)=>{results.push(result);returnresults;});},Promise.resolve());}

练习 3:带超时的 fetch 包装

写一个fetchWithTimeout函数,在指定时间内未完成则报错:

fetchWithTimeout("/api/data",3000).then((data)=>console.log(data)).catch((err)=>console.log("超时或失败"));
参考答案
functionfetchWithTimeout(url,timeout){returnPromise.race([fetch(url).then((r)=>r.json()),newPromise((_,reject)=>setTimeout(()=>reject(newError("请求超时")),timeout))]);}

八、AI 辅助学习

8.1 本节知识点的 AI 提问模板

【背景】我是 JavaScript 初学者,正在学习第 23 篇"异步编程:回调函数与 Promise"。 我已经了解同步与异步的基本区别,以及回调函数的概念。 【问题】我理解 Promise 可以链式调用避免回调地狱,但在实际开发中, Promise 的错误处理应该怎么组织?如果链中的某个 then 抛出了异常, 后续代码会怎样执行? 【期望】请解释 Promise 链中的错误传播机制,给出 3 种常见的错误处理模式 (集中 catch、分段 catch、finally 清理),并说明各自适用场景。

8.2 用 AI 验证你的理解

  • 问 AI:“new Promise里面的代码是同步还是异步执行的?”
  • 让 AI 比较:Promise.allPromise.allSettled的区别是什么?
  • 让 AI 出题:“写一道关于 Promise 链中 then 返回值处理的面试题”

8.3 警惕 AI 的常见错误

  • AI 可能声称 Promise 会创建新线程
  • AI 可能在new Promise的 executor 中忘记 resolve/reject
  • AI 可能在 then 中抛出错误后忘记后续 catch 能捕获
  • AI 可能混淆Promise.allPromise.race的行为

九、配套代码

本文示例代码位于:CODE/23-异步编程/

文件名说明
async-lab.html异步编程实验室:回调演示、Promise 链式调用、all/race/allSettled 对比

十、本章小结

  • 异步编程:单线程下处理耗时操作不阻塞主线程
  • 回调函数:作为参数传入,操作完成后调用,易形成回调地狱
  • Promise:代表未来会完成的操作,有 pending/fulfilled/rejected 三种状态
  • 链式调用:then 返回新 Promise,可继续链式调用,代码扁平化
  • 组合方法Promise.all(全完成)、Promise.race(取最快)、Promise.allSettled(全结果)
  • 回调转 Promise:将回调风格 API 包装为返回 Promise 的函数

十一、下篇预告

下一篇继续异步编程:《async/await:异步代码的同步写法》,你将学到:

  • async 函数和 await 表达式
  • async/await 与 Promise 的关系
  • 错误处理:try/catch 在异步中的用法
  • 串行 vs 并发的正确写法

如果本文对你有帮助,欢迎点赞、收藏、关注专栏。有任何问题可以在评论区交流!

http://www.gsyq.cn/news/1567996.html

相关文章:

  • Unlock Music:3分钟学会在浏览器中解锁加密音乐
  • 如何在5分钟内为《绝地求生》搭建专业级战场雷达系统
  • 在Mac上运行Windows软件:终极简单指南,告别虚拟机烦恼![特殊字符]
  • 省内电动车托运防坑:2026短途寄运避骗技巧 - 快递物流资讯
  • 混沌特征变换:小样本图像分类中的特征空间增强新思路
  • 宇树科技 U2
  • MIFARE系统安全:从芯片认证到纵深防御的实战设计
  • Claude Code接入国产大模型:适配层开发与vLLM代理实战
  • 32位MCU平台化设计:从内核选型到低功耗外设的嵌入式开发实战
  • 从KE0x到KE1x:嵌入式平台迁移实战与Kinetis SDK应用指南
  • 多智能体AI如何协同挖掘可穿戴数据,发现新型数字生物标志物
  • OpenClaw与nanobot:构建高效UI自动化测试的编排与执行方案
  • 2026年铝包木门窗知名品牌推荐 - 谁都没有我好看
  • 2026深耕郑州西区传动维修市场!中原区创越变速箱专修全品类覆盖燃油与新能源,打造本地靠谱变速箱养护基地 - GrowthUME
  • Ubuntu 16.04部署TigerVNC远程桌面实战指南
  • Kemono-scraper:打造专业级数字艺术内容管理流水线
  • DSP56300/56600优化实战:从架构理解到代码极致性能调优
  • UI自动化测试:XPath与CSS Selector定位技术深度解析
  • 留学党必看!Turnitin降AI率平台TOP5实测中英文论文AI率压到 10% 以下
  • 从6周期到0.75周期:DSP复数乘法内核优化实战与性能极限逼近
  • Linux Shell本质解析:sh、bash、zsh语法兼容性与跨平台执行原理
  • 宁波企业AI获客必看:2026本地TOP5 GEO优化公司甄选,实战效果可量化 - 936品牌测评网
  • 豆包练英语:免费AI语言教练的实战训练法
  • 802.15.4 MAC安全配置实战:Freescale协议栈组密钥星型网络详解
  • Comic Backup:从在线漫画到本地CBZ的完整解决方案
  • 石家庄黄金回收店哪家正规?2026年6月实测7家门店,避坑指南来了 - 天天生活分享日志
  • 国产大模型合规使用指南:API调用与提示词优化实战
  • 9 系列 SUV 车型推荐:大六座旗舰配置性能与选购方向全解析 - 外贸老黄
  • 硚口丰诺自动变速箱以修代换,解决顿挫打滑各类故障 - GrowthUME
  • MPC564x双核MCU性能优化实战:从Flash等待状态到交叉开关配置