JavaScript中的Concurrency并发:异步操作下的汉堡制作示例
这篇文章想讲一下JavaScript中同步与异步操作在一个简单的示例中的应用。我们将以制作汉堡为例,展示如何使用同步方法、回调函数(callbacks)和Promise与async/await来实现该过程。
让我们想象一下我们正在制作汉堡:
1. 获取原料(比如beef)
2. 烹饪牛肉
3. 获得面包片
4. 把做好的牛肉放进面包片
5. 提供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既支持异步操作,又具有良好的可读性,因此更推荐这两种写法。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了