JavaScript中的Concurrency并发:异步操作下的汉堡制作示例
这篇文章想讲一下JavaScript中同步与异步操作在一个简单的示例中的应用。我们将以制作汉堡为例,展示如何使用同步方法、回调函数(callbacks)和Promise与async/await来实现该过程。
Let’s imagine we’re trying to make a burger:
1. Get ingredients 获取原料(比如beef)
2. Cook the beef 烹饪牛肉
3. Get burger buns 获得面包片
4. Put the cooked beef between the buns 把做好的牛肉放进面包片
5. Serve the burger 提供burger
接下来我们用不同方法来演示这几个步骤的实现。
1. 同步方法
首先,我们来看一个使用同步方法实现汉堡制作过程的示例:
function getBeef() {console.log("Step 1: Getting beef");return "beef";
}function cookBeef(beef) {console.log("Step 2: Cooking beef");if (beef === "beef") return "patty";
}function getBuns() {console.log("Step 3: Getting buns");return "buns";
}function putBeefBetweenBuns(buns, patty){if (buns === "buns" && patty === "patty") {console.log("Step 4: Putting beef patty between buns");return "burger"}
}function makeBurger() {const beef = getBeef();const patty = cookBeef(beef);const buns = getBuns();const burger = putBeefBetweenBuns(buns, patty);return burger;
}function serve(burger){console.log("Finally: " + burger + " is served!")
}const burger = makeBurger();
serve(burger);
在这个示例中,我们使用了同步方法来实现汉堡制作的各个步骤。这种方法非常简单,没什么可以讲的,但在处理复杂任务时可能会受限,因为它不支持异步操作。
2. 回调函数(Callbacks)
接下来,我们来看一个使用回调函数实现汉堡制作过程的示例:
function getBeef(cb) {setTimeout(() => {console.log("Step 1: Getting beef");const beef = "beef";cb(beef);}, 1000);
}function cookBeef(beef, cb) {setTimeout(() => {if(beef === "beef"){console.log("Step 2: Cooking beef");const patty = "patty"cb(patty)}}, 1000)
}function getBuns(cb) {setTimeout(() => {console.log("Step 3: Getting buns");const buns = "buns";cb(buns)}, 1000)
} function putBeefBetweenBuns(buns, patty, cb) {setTimeout(() => {if (buns === "buns" && patty === "patty") {console.log("Step 4: Putting beef patty between buns");const burger = "burger"cb(burger)}}, 1000);
}//关键部分
function makeBurger(cb) {getBeef(function(beef) {cookBeef(beef, function(patty)getBuns(function(buns) {putBeefBetweenBuns(buns, patty, function(burger) {cb(burger);});});});
});
}function serve(burger){
console.log("Finally: Burger is served!")
}// Make and serve the burger
makeBurger((burger) => {
serve(burger)
})
为了理解上面的例子,先说一下 setTimeout
函数,setTimeout
是一个 JavaScript 函数,它用于在指定的时间延迟后执行某个函数或代码片段。setTimeout
本身不会阻塞代码执行,它会将提供的回调函数(需要延迟执行的代码)放入一个队列中,在计时器结束后将其放入事件循环,等待执行。让我给你一个简单的例子来解释 setTimeout
的工作原理:
console.log("Before setTimeout");setTimeout(() => {console.log("Inside setTimeout");
}, 2000);console.log("After setTimeout");
执行这段代码时,你会发现输出顺序如下:
Before setTimeout
After setTimeout
大约 2 秒后,会出现:Inside setTimeout
在第二个制作汉堡的示例中,我们使用回调函数来处理异步操作。
回调函数就是一个作为参数传递给另一个函数的函数。当被调用函数完成其操作后,它会执行传递的回调函数。在这个汉堡制作示例中,我们将回调函数用于每个步骤的完成通知。
下面是回调函数示例的详细解释:
makeBurger()
函数被调用,它首先调用getBeef()
函数,并将一个匿名回调函数作为参数传递。这个回调函数接收一个参数beef
。getBeef()
函数执行异步操作(使用setTimeout()
模拟),当操作完成后,它调用传递的回调函数,并将beef
作为参数传递。- 回调函数执行,并使用
beef
参数调用cookBeef()
函数。同样,我们为cookBeef()
函数传递一个匿名回调函数,接收一个参数patty
。 cookBeef()
函数执行异步操作,当操作完成后,它调用传递的回调函数,并将patty
作为参数传递。- 回调函数执行,并使用
patty
参数调用getBuns()
函数。我们为getBuns()
函数传递一个匿名回调函数,接收一个参数buns
。 getBuns()
函数执行异步操作,当操作完成后,它调用传递的回调函数,并将buns
作为参数传递。- 回调函数执行,并使用
buns
和patty
参数调用putBeefBetweenBuns()
函数。我们为putBeefBetweenBuns()
函数传递一个匿名回调函数,接收一个参数burger
。 putBeefBetweenBuns()
函数执行异步操作,当操作完成后,它调用传递的回调函数,并将burger
作为参数传递。- 回调函数执行,并将
burger
参数传递给serve()
函数。 serve()
函数打印消息,表明汉堡已经制作完成并上菜。
在这个过程中,我们可以看到回调函数在每个异步操作完成后执行,并将结果传递给下一个操作。这使我们能够以异步方式处理整个汉堡制作过程。然而,这种方法的缺点是回调函数可能导致嵌套层数过多,导致代码可读性降低。甚至不仔细看很久都看不懂的程度,所以别担心如果你看不懂这个代码,JavaScript本身语法和可读性可理解性我觉得就是垃圾至极,看不懂别有心理负担,反正我们要避免类似上面的这种Callback Hell, 我们最常用的也是下面的这种方法:
3. Promise 与 async/await
function getBeef() {return new Promise((res) => {setTimeout(() => {console.log("Step 1: Getting beef");res("beef");}, 1000);});
}function cookBeef(beef) {return new Promise((res, rej) => {setTimeout(() => {if (beef === "beef"){console.log("Step 2: Cooking beef");res("patty");}else rej("no beef available");}, 1000);});
}function getBuns() {return new Promise((res) => {setTimeout(() => {console.log("Step 3: Getting buns");res("buns");}, 1000);});
}function putBeefBetweenBuns(buns, patty) {return new Promise((res, rej) => {setTimeout(() => {if (buns !== "buns") rej("no buns");else if (patty !== "patty") rej("no patty");else{console.log("Step 4: Putting beef patty between buns");res("burger");} }, 1000);});
}//Promise链式调用
getBeef().then(beef => {return Promise.all([cookBeef(beef),getBuns()])
}).then(ingredients => {const [patty, buns] = ingredients;return putBeefBetweenBuns(buns, patty)
}).then(burger => {console.log("Finally: " + burger + " is served!")
})//async/await
async function makeBurger(){const beef = await getBeef();const patty = await cookBeef(beef);const buns = await getBuns();const burger = await putBeefBetweenBuns(buns, patty);return burger
}
makeBurger()
在这个示例中,我们使用了Promise的链式调用(用then关键字)以及async/await来处理异步操作。
1.对于Promise链式调用,通过 .then()
方法将异步操作链接在一起。在这个例子中,首先调用 getBeef()
,然后在 .then()
中处理获得的 beef
。接着,我们使用 Promise.all()
将 cookBeef(beef)
和 getBuns()
一起执行,等待它们都完成后,再处理它们的结果。最后,我们将 patty
和 buns
放在一起组成 burger
。这种写法的优点是:它允许你以更加清晰的方式组织异步操作。但是,当异步操作非常多时,这种写法可能导致 .then()
链过长,从而使代码变得难以阅读和维护。
2.对于async/await,它允许你以更接近同步代码的方式编写异步操作。在这个例子中,我们使用 await
等待每个异步操作完成,并将结果赋值给相应的变量。这样,代码看起来就像是同步执行的,但实际上仍然是异步的。这种写法的优点是:它使代码更加简洁和易于阅读。此外,它可以让你更容易地处理错误,因为你可以直接使用 try-catch
语句捕获异步操作中的异常。
这两种写法都用于处理异步操作,它们的主要区别在于书写风格和可读性。Promise 链式调用更注重将操作链接在一起,而 async/await 更侧重于使代码看起来像同步执行。
总结
在这篇博客中,我们比较了在JavaScript中实现同步和异步操作的三种方法:同步方法、异步中的回调函数和Promise链式调用与async/await。每种方法都有其优缺点。同步方法易于理解,但不支持异步操作;回调函数支持异步操作,但可读性差;而Promise与async/await既支持异步操作,又具有良好的可读性,因此更推荐这两种写法。
相关文章:

JavaScript中的Concurrency并发:异步操作下的汉堡制作示例
这篇文章想讲一下JavaScript中同步与异步操作在一个简单的示例中的应用。我们将以制作汉堡为例,展示如何使用同步方法、回调函数(callbacks)和Promise与async/await来实现该过程。 Let’s imagine we’re trying to make a burger: 1. Get …...

微信小程序开发一个多少钱
小程序开发是当前比较流行的一项技术服务,能够为企业和个人带来巨大的商业价值和社会价值,但是小程序开发费用也是潜在的成本之一。在选择小程序开发服务时,了解开发费用如何计算、影响价格的因素以及如何降低成本等方面的知识,可…...

Python基础入门(2)—— 什么是控制语句、列表、元组和序列?
文章目录 01 | 🚄控制语句02 | 🚅列表03 | 🚈元组04 | 🚝序列05 | 🚞习题 A bold attempt is half success. 勇敢的尝试是成功的一半。 前面学习了Python的基本原则、变量、字符串、运算符和数据类型等知识,…...

计算机专业大一的一些学习规划建议!
大家好,我是小北。 五一嗖的一下就过啦~ 对于还在上学的同学五一一过基本上意味着这学期过半了,很多大一、大二的同学会有专业分流、转专业等事情。 尤其是大二的时候,你会发现身边有些同学都加入各种实验室了,有忙着打ACM、学生…...

万万没想到在生产环境翻车了,之前以为很熟悉 CountDownLatch
前言 需求背景 具体实现 解决方案 总结 前言 之前我们分享了CountDownLatch的使用。这是一个用来控制并发流程的同步工具,主要作用是为了等待多个线程同时完成任务后,在进行主线程任务。然而,在生产环境中,我们万万没想到会…...

Springboot整合Jasypt实战
Springboot整合Jasypt实战 引入依赖 <dependency><groupId>com.github.ulisesbocchio</groupId><artifactId>jasypt-spring-boot-starter</artifactId><version>3.0.5</version> </dependency>配置jasypt # 配置jasypt相关信息…...

计算机网络笔记:DNS域名解析过程
基本概念 DNS是域名系统(Domain Name System)的缩写,也是TCP/IP网络中的一个协议。在Internet上域名与IP地址之间是一一对应的,域名虽然便于人们记忆,但计算机之间只能互相认识IP地址,域名和IP地址之间的转…...

C语言函数大全-- s 开头的函数(4)
C语言函数大全 本篇介绍C语言函数大全-- s 开头的函数(4) 1. strdup 1.1 函数说明 函数声明函数功能char * strdup(const char *s);用于将一个以 NULL 结尾的字符串复制到新分配的内存空间中 注意: strdup() 函数返回指向新分配的内存空间…...

Linux常见指令 (2)
Linux常见指令 ⑵ 补充man描述:用法:例子 echo描述:用法:例子 echo 字符串例子 echo 字符串 > 文件例子 追加重定向(>>)例子 输出重定向(>)来创建文件 && (>)来清空文件 cat描述:用法:例子 cat && cat 文件补充:例子 cat 文件 && cat &…...
shell脚本4
字符串变量 格式介绍:单引号 varabc 双引号 var"abc" 不使用引号 varabc 区别:单引号,原样输出,不会解析里面的变量 双引号,会解析变量,并且可以使用子双引号,需要转…...

递归思路讲解
最近刷到了树这一模块的算法题,树相关的算法题几乎都是用递归来实现的,但递归的思路却有点抽象,每次遇到递归,都是通过递归来深度或广度地遍历树,但对于递归遍历树的遍历路线,却有点抽象难懂,不…...
基于R语言APSIM模型高级应用及批量模拟
目录 专题一 APSIM模型应用与R语言数据清洗 专题二 APSIM气象文件准备与R语言融合应用 专题三 APSIM模型的物候发育和光合生产模块 专题四 APSIM物质分配与产量模拟 专题五 APSIM土壤水平衡模块 专题六 APSIM土壤碳、氮平衡模块 专题七 APSIM农田管理模块与情景模拟 专…...
Hyperf中的其它事项
Hyperf中的其它事项 关于 Hyperf 其它的内容我们就不多说了,毕竟框架这东西用得多了自然也就熟悉了。最重要的是——我的水平还不足以去深入地分析这个框架! 好吧,其它的功能大家可以去官方文档详细了解,毕竟国人自己做的框架&a…...

【技术选型】Elasticsearch 和Solr那个香?
我们为什么在这里?我存在的目的是什么?我应该运动还是休息并节省能量?早起上班或晚起并整夜工作?我应该将炸薯条和番茄酱或蛋黄酱一起吃吗? 这些都是古老的问题,可能有也可能没有答案。其中一些是非常困难或…...

4面美团测试工程师,因为这个小细节,直接让我前功尽弃.....
说一下我面试别人时候的思路 反过来理解,就是面试时候应该注意哪些东西;用加粗部分标注了 一般面试分为这么几个部分: 一、自我介绍 这部分一般人喜欢讲很多,其实没必要。大约5分钟内说清楚自己的职业经历,自己的核…...

数据恢复软件EasyRecovery16下载安装步骤教程
EasyRecovery16是一款专业好用的数据恢复软件,软件提供了向导式的操作向导,可以有效地恢复电脑或者移动存储设备中丢失的各种文件,包括删除的文件、格式化丢失的文件和清空回收站的数据!千呼万唤始出来,大家期盼许久的EasyRecover…...

Springboot 自定义缓存配置 CacheManager 及redis集成
目录 前言 集成 maven依赖 CacheManagerConfig配置 redis配置 使用 Springboot 集成使用缓存 Cacheable CacheEvict 前言 现有项目中经常遇到的缓存集成问题,Springboot提供了统一的接口抽象与缓存管理器,可集成多种缓存类型,如 Co…...
JS 中七个改变原数组的方法
目录 一、push 二、pop 三、unshift 四、shift 五、splice 六、sort 七、reverse 一、push 在数组的尾部添加元素,并返回新的长度。 let arr [1] arr.push(2) console.log(arr) // [1, 2] 二、pop 删除数组最后面一个元素、并返回删除的元素。 let arr [1, …...

【笔试强训选择题】Day7.习题(错题)解析
作者简介:大家好,我是未央; 博客首页:未央.303 系列专栏:笔试强训选择题 每日一句:人的一生,可以有所作为的时机只有一次,那就是现在!!! 文章目录…...

Vue电商项目--axios二次封装
postman测试接口 刚刚经过postman工具测试,发现接口果然发生了改变。 新的接口为http://gmall-h5-api.atguigu.cn 如果服务器返回的数据code字段200,代表服务器返回数据成功 整个项目,接口前缀都有/api字样 axios二次封装 XmlHttpRequ…...

测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...

从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...

MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

(二)TensorRT-LLM | 模型导出(v0.20.0rc3)
0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...

HBuilderX安装(uni-app和小程序开发)
下载HBuilderX 访问官方网站:https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本: Windows版(推荐下载标准版) Windows系统安装步骤 运行安装程序: 双击下载的.exe安装文件 如果出现安全提示&…...