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

JavaScript 异步编程:Callback、Promise、async/await

一、为什么异步编程在JS中如此重要

在 JavaScript 的世界里,异步编程几乎是呼吸般的存在。 这门语言最初诞生于浏览器,为的是让网页在用户操作的同时还能去加载数据、响应事件、渲染动画——而这一切都运行在单线程之上。

单线程意味着同一时间只能做一件事,如果某个任务耗时过长(比如网络请求、文件读取、复杂计算),整个页面就会“卡死”,按钮点不动、动画停滞、用户体验瞬间崩塌。

异步编程正是为了解决这个问题而生:

  • 让 JavaScript 可以先挂起耗时任务,继续处理其他工作,等结果准备好再回来执行。

  • 让我们能同时监听用户输入、加载数据、播放动画,而不会互相阻塞。

  • 是现代 Web 应用、Node.js 服务、跨平台应用流畅运行的基石。

二、Callback回调函数

回调函数定义:被作为参数传递的函数就成为回调函数。

回调函数名字虽然抽象,但我们可以将其理解为“回头再调用的函数”,代表着我交给你一个函数,在任务完成后可以回头调用。实现了简单的异步编程。

优点:

  • 回调函数简单直接,易于实现
  • 让指定函数在恰当时机,以恰当触发条件被触发
  • 让函数更灵活,可以按照实际需要调整函数
  • 提高程序效率,如我需要在指定时间触发某函数,在不使用回调函数的情况,我需要在函数内不断查询当前时间,不仅效率低下还会使线程堵塞。而使用回调函数就可以查询当前时间后,计算还剩多少时间,使用setTimeOut()函数触发,在触发指定函数前将线程让给其他程序。

简单来说,触发程序就像餐馆上餐,这份餐就是程序运行的结果。而以往我们需要不停询问厨房是否做好,使用回调函数就像厨房给了我们一份呼号机,出餐后通知我们并上餐。

缺点:

  • 如果我们需要使用多个回调函数,需要在每一层2函数中层层嵌套,使得代码难以阅读和维护,运行结果处理分散,形成回调地狱。
// 回调地狱:层层嵌套的回调(Callback Hell)
function callbackHell(userId, done) {readConfig((err, config) => {if (err) return done(err);connectDb(config.db, (err, conn) => {if (err) return done(err);findUser(conn, userId, (err, user) => {if (err) return done(err);fetchProfile(user.token, (err, profile) => {if (err) return done(err);transformData(profile, (err, report) => {if (err) return done(err);saveReport(config.output, report, (err) => {if (err) return done(err);done(null, "完成:报告已生成");});});});});});});
}// ——— 模拟的异步函数们 ———
function readConfig(cb) {setTimeout(() => {console.log("[readConfig]");// 模拟成功cb(null, { db: "db://example", output: "report.txt" });}, 100);
}function connectDb(uri, cb) {setTimeout(() => {console.log("[connectDb]", uri);cb(null, { uri, connected: true });}, 120);
}function findUser(conn, userId, cb) {setTimeout(() => {console.log("[findUser]", userId);// 模拟可能失败if (userId == null) return cb(new Error("缺少 userId"));cb(null, { id: userId, token: "token-abc" });}, 80);
}function fetchProfile(token, cb) {setTimeout(() => {console.log("[fetchProfile]", token);cb(null, { name: "Alice", age: 28 });}, 150);
}function transformData(profile, cb) {setTimeout(() => {console.log("[transformData]");try {const report = `User: ${profile.name}, Age: ${profile.age}`;cb(null, report);} catch (e) {cb(e);}}, 60);
}function saveReport(path, content, cb) {setTimeout(() => {console.log("[saveReport]", path, "->", content);cb(null);}, 70);
}// 运行示例
callbackHell(42, (err, msg) => {if (err) {console.error("失败:", err.message);} else {console.log("成功:", msg);}
});

可以看到,如果运行出错,我们难以在层层嵌套中debug。

为了解决回调地狱,我们在ES6中引入了Promise。

三、Promise

Promise人如其名,代表承诺,即:我承诺无论我的内部程序是否正常运行,会在未来某个时候给你一个结果。

设计动机:解决回调地狱,统一错误处理。

核心概念:状态(pending-处理中/fullfilled-已解决/rejected-已拒绝)

基本用法:new Promise(创建promise对象)、resolve(传递并包装成功结果)、rejected(传递错误结果)

//在函数内部:
resolve('这里内容会被作为成功结果传出')reject('这里结果会被作为错误结果传出')

链式调用:

  • .then(res=>{}):
    接受上一级的成功结果,res为接受的参数,同时若有需要再向下调起下一级函数。
  • .catch(err=>{}):
    接受所有位置的错误结果,err为接收到的错误,同时自定义发生错误后程序怎样执行。
  • .finally(()=>{}):
    不接收任何参数,无论程序运行成功与否都会运行,适合做收尾工作而不是处理结果:如关闭连接,隐藏元素等。

使用Promise重写回调地狱(promiseFlow即为callBackHell的重置):

function promiseFlow(userId) {return readConfig().then(config => {return connectDb(config.db).then(conn => ({ config, conn }));}).then(({ config, conn }) => {return findUser(conn, userId).then(user => ({ config, user }));}).then(({ config, user }) => {return fetchProfile(user.token).then(profile => ({ config, profile }));}).then(({ config, profile }) => {return transformData(profile).then(report => ({ config, report }));}).then(({ config, report }) => {return saveReport(config.output, report);});
}function readConfig() {return new Promise((resolve, reject) => {setTimeout(() => {console.log("[readConfig]");resolve({ db: "db://example", output: "report.txt" });}, 100);});
}function connectDb(uri) {return new Promise((resolve, reject) => {setTimeout(() => {console.log("[connectDb]", uri);resolve({ uri, connected: true });}, 120);});
}function findUser(conn, userId) {return new Promise((resolve, reject) => {setTimeout(() => {console.log("[findUser]", userId);if (userId == null) return reject(new Error("缺少 userId"));resolve({ id: userId, token: "token-abc" });}, 80);});
}function fetchProfile(token) {return new Promise((resolve, reject) => {setTimeout(() => {console.log("[fetchProfile]", token);resolve({ name: "Alice", age: 28 });}, 150);});
}function transformData(profile) {return new Promise((resolve, reject) => {setTimeout(() => {console.log("[transformData]");try {const report = `User: ${profile.name}, Age: ${profile.age}`;resolve(report);} catch (e) {reject(e);}}, 60);});
}function saveReport(path, content) {return new Promise((resolve, reject) => {setTimeout(() => {console.log("[saveReport]", path, "->", content);resolve();}, 70);});
}// 运行
promiseFlow(42).then(() => {console.log("成功:报告已生成");}).catch(err => {console.error("失败:", err.message);});

四、async/await语法糖

本质是Promise函数的语法糖,使Promise函数更具有可读性,结构更接近同步代码。

用法:async返回Promise函数,await等待Promise结果(await只能写在async内部)。

错误处理:try/catch,try中如果出现了错误,catch就会捕捉。


// async/await 版本的流程
async function asyncFlow(userId) {try {const config = await readConfig();const conn = await connectDb(config.db);const user = await findUser(conn, userId);const profile = await fetchProfile(user.token);const report = await transformData(profile);await saveReport(config.output, report);console.log("成功:报告已生成");} catch (err) {console.error("失败:", err.message);}
}//函数定义与Promise写法相同// 运行
asyncFlow(42);

其中,await意思是:需要等待来得到结果,得到结果后就会告诉函数体。

改进:

  • 结构线性,看起来像同步代码,顺序一目了然。
  • 错误集中处理,包裹整个流程。
  • 变量作用域更加清晰。

五、对比

演化路线

  • 回调 → 最原始的异步方式,但容易混乱。

  • Promise → 解决回调地狱,提供链式调用和状态管理。

  • async/await → 在 Promise 基础上进一步简化,让异步代码像同步一样易读。

特性回调函数 CallbackPromiseasync/await
可读性差(嵌套多)中等
错误处理分散在回调中.catch 统一try...catch 统一
状态管理有状态(pending/fulfilled/rejected)基于 Promise 状态
语法复杂度中等低(最直观)
适用场景简单异步任务中等复杂度任务复杂异步流程
http://www.dtcms.com/a/362304.html

相关文章:

  • 知识表示与处理1
  • 【光照】Unity中的[光照模型]概念辨析
  • 精确率、召回率、漏检率、误判率
  • 基于单片机倒车雷达/超声波测距设计
  • 《零基础入门AI:YOLOv3、YOLOv4详解》
  • React中纯 localStorage 与 Context + useReducer + localStorage对比
  • 【笔记】大模型训练(一)单卡训练的分析与优化策略
  • 微信小程序开发-day1
  • 一次诡异的报错排查:为什么时间戳变成了 ١٧٥٦٦٣٢٧٨
  • 9.1日IO作业
  • 大模型RAG项目实战:文本向量模型>Embedding模型、Reranker模型以及ColBERT模型
  • nCode 后处理常见问题汇总
  • 生成知识图谱与技能树的工具指南:PlantUML、Mermaid 和 D3.js
  • 过拟合 正则化(L1,L2,Dropout)
  • linux内核 - 文件系统相关的几个概念介绍
  • Ceres学习笔记
  • 从理论到RTL,实战实现高可靠ECC校验(附完整开源代码/脚本)(3) RTL实现实战
  • 智慧班牌系统基于Java+Vue技术栈构建,实现教育信息化综合管理。
  • ES6手录01-let与const
  • 2024 年 AI 技术全景图:大模型轻量化、多模态融合如何重塑产业边界?
  • c#:抽象类中的方法
  • Windows 使用 Compass 访问MongoDb
  • 笔记:现代操作系统:原理与实现(1)
  • 利用本地电脑上的MobaXterm连接虚拟机上的Ubuntu
  • 【Python知识】Playwright for Python 脚本录制指南
  • Nature Communications发布智能光电探测研究:实现0.3-1.1 THz波段强度-偏振-频率连续高维感知
  • 第7.6节:awk语言 break 语句
  • 刷题日记0901
  • 动态代理设计模式
  • 从Redisson分布式锁看锁的设计思路