🔥披萨还没到你就吃了?”JavaScript异步编程入门全解🔥

前言:JavaScript 异步编程,从"懵"到"懂"

JavaScript 是一门以单线程 为核心的语言,这意味着它一次只能做一件事。然而,正是这个"一次只能做一件事"的限制,催生了 JavaScript 最强大也最让人头疼的特性之一:异步编程

我们每天都在写异步代码,比如发起网络请求、操作 DOM、处理用户交互、甚至只是一个简单的 setTimeout。但你是否真正理解:

  • 什么是同步、什么是异步?
  • 回调函数为什么容易"嵌套地狱"?
  • Promise 为什么被称为"异步编程的救星"?
  • async/await 又是怎么做到让异步代码像同步代码一样优雅?

很多人初学 JavaScript 时,都会从最简单的同步代码入手。但一旦碰到异步逻辑,就容易陷入"执行顺序搞不清、回调嵌套写不明白、Promise 不知道怎么链式调用"的困惑。

这篇文章,就是为你写的。

这一期我们将从最基础的 同步与异步 讲起,带你一步步理解 JavaScript 是如何处理"等待"这件事的。然后在下一期我们会深入讲解 Promise 的原理与用法 ,最后升华到 async/await 的优雅写法,帮助你从"写异步代码"变成"写好异步代码"。

无论你是刚入门的新手,还是已经写过几年代码但对异步机制始终"似懂非懂"的开发者,只要你愿意静下心来读完这篇文章,相信你都能收获一份"豁然开朗"的理解。

同步?异步?

在 JavaScript 中,代码的执行方式可以分为两种:同步(Synchronous)异步(Asynchronous) 。这两种方式就像两个人做事情的方式一样,一个"按顺序一步步来",一个"边做边等"不耽误进度。

同步(Synchronous)

同步就是我们最熟悉、最直观的执行方式:按顺序一行一行地执行代码,前一行没执行完,后一行就得等着。 我们平时熟悉的,一般都是同步代码。

js 复制代码
console.log("第一步");
console.log("第二步");
console.log("第三步");
// 按顺序输出

就像这一段代码一样,引擎从上到下逐步执行语句,每一句代码的执行都必须等上一句代码执行完毕 ,这就是同步

异步(Asynchronous)

但现实世界中,并不是每件事都得"等前面做完才行",比如:

你点了一份外卖,总不能一直站在门口等它送过来吧?你可以一边等,一边刷会儿手机。

JavaScript 在浏览器中面对类似场景(比如网络请求、计时器、文件读取等耗时操作)时,也采用了这种"边做边等"的方式,就是异步

js 复制代码
var pizza;
function getPizza() {
    setTimeout(() => {
        pizza = '🍕',
        console.log(`${pizza}到了`)
    }, 2000)
}
console.log('开始订披萨')
getPizza();
console.log('玩会手机')
// 执行结果: 开始订披萨
//           玩会手机
//           披萨到了

虽然 setTimeout 写在中间,但它并不会阻止后面的代码执行。2 秒后才会输出"🍕到了"。这说明 JavaScript 的代码执行是非阻塞的,这就是异步的核心思想。

简单来说,同步/异步的执行就像下图一样:

假设这两条是程序运行的线路 ,代表着程序从开始到终止的全过程

这两条线路就是负责处理同步/异步任务的不同线路,当执行到异步任务时就扔出去,交给另一条线路处理,自己继续处理剩下的任务。

就像你在吃大餐的时候,你讨厌吃海鲜,但是你的朋友喜欢吃海鲜,当你吃着吃着吃到了海鲜的时候,你就把盘子里的海鲜给了他,让他来解决,然后你继续吃剩下的饭。

为什么 JavaScript 要用异步?

JavaScript 最初设计时就是单线程语言,也就是说它同时只能做一件事。如果所有的操作都同步执行,那遇到一个耗时操作(比如读取文件、发送网络请求),整个页面就会"卡住",用户体验极差。

相信你也不想在抢票的时候页面卡上个几分钟吧hhhhhhhhh~

所以 JavaScript 引入了 异步机制 ,通过事件循环(Event Loop) 来处理异步任务,让程序在等待某些操作完成的同时,可以继续执行其他任务,从而提高效率、提升用户体验

你写的内容整体上逻辑清晰、案例生动,已经非常接近一篇优秀的技术讲解文章了。但正如你自己所说,标题和部分内容表达略显混乱,重点不突出,可以进一步优化结构和语言表达,使其更具条理性、逻辑性,也更容易理解。

如何正确使用异步编程?

大家已经对 同步与异步 有了基本认识。那么,如何在实际开发中正确地使用异步编程呢?

我们来看一个生活化的场景:你想要订一个披萨 ,然后打电话叫朋友 Fade ,之后一起享用披萨

这个场景中,取披萨 是一个耗时的操作 (异步),而打电话吃披萨后续的同步操作 。我们要确保:在披萨送到之后,才执行吃披萨的动作

异步与同步代码混用导致逻辑错误

我们尝试用同步思维来写这段代码:

js 复制代码
var pizza;

function getPizza() {
    setTimeout(() => {
        pizza = '🍕';
        console.log(`${pizza}到了`);
    }, 2000);
}

console.log('开始订披萨');
getPizza();
console.log('Call Fade');
console.log(`Eat ${pizza} with Fade`);

输出结果是:

javascript 复制代码
开始订披萨
Call Fade
Eat undefined with Fade
🍕到了

我们可以看到:在披萨还没送到的时候,就已经执行了"吃披萨"的操作,这显然是不合理的。


解决方案:使用回调函数(Callback)

为了解决这个问题,我们需要一种机制,来保证:只有在异步任务完成后,才执行后续操作

JavaScript 最早的解决方案是:使用 回调函数(Callback)


什么是回调函数?

回调函数 是一个函数,作为参数传给另一个函数,并在该函数内部被调用。它常用于处理 异步操作,例如:网络请求、定时任务、文件读取等。


使用回调函数重构代码

我们把"吃披萨"这个动作封装成函数,并传入到 getPizza 中,确保它在披萨送达后再执行:

js 复制代码
var pizza;

function getPizza(callback) {
    setTimeout(() => {
        pizza = '🍕';
        console.log(`${pizza}到了`);
        callback(); // 关键:表示披萨已经准备好,可以执行后续操作
    }, 2000);
}

function eatPizza() {
    console.log(`Eat ${pizza} with Fade`);
}

console.log('开始订披萨');
getPizza(eatPizza);
console.log('Call Fade');

输出结果:

sql 复制代码
开始订披萨
Call Fade
🍕到了
Eat 🍕 with Fade

完美! 现在逻辑正确了:先订披萨 → 打电话 → 披萨送到 → 吃披萨


回调函数的本质

回调函数的本质是:告诉异步函数"你做完这件事之后该干什么"

通过把"下一步任务"作为函数传入当前任务,我们可以实现异步操作之间的顺序控制


回调地狱

回调地狱(Callback Hell) ,又称为"回调金字塔(Pyramid of Doom) ",指的是多个嵌套的回调函数形成的代码结构,导致代码难以阅读、维护和调试。

再拿上面的披萨场景举例子:

js 复制代码
function thing1(){
    console.log('拿起电话');
    
}
function thing2(){
    console.log('打给披萨店');
    
}
function thing3(){
    console.log('点单');
    
}
function thing4(){
    console.log('吃披萨');
    
}

如果我想吃个披萨该怎么办呢?那就应该按照1,2,3,4这四步去做,每一步都依靠上一步 ,但是每一步都是耗时的任务。 那就有:

js 复制代码
function thing1(callback){
    console.log('拿起电话');
    callback()
}
function thing2(callback){
    console.log('打给披萨店');
     callback()
}
function thing3(callback){
    console.log('点单');
     callback()
}
function thing4(){
    console.log('吃披萨');
    
}
thing1(() => {
    thing2(() => {
        thing3(() => {
            thing4();
        });
    });
});
//输出: 拿起电话
//      打给披萨店
//      点单
//      吃披萨

这就是回调地狱,看看是不是可读性非常的差,看过去就不好理解发生了什么事情了~

下面还有个更难看的:

js 复制代码
fs.readFile('file1.txt', 'utf8', function(err, data1) {
    if (err) return console.error(err);

    fs.readFile('file2.txt', 'utf8', function(err, data2) {
        if (err) return console.error(err);

        fs.readFile('file3.txt', 'utf8', function(err, data3) {
            if (err) return console.error(err);

            console.log(data1 + data2 + data3);
        });
    });
});

总结

  • 同步代码是线性执行的,异步代码不会阻塞主线程。
  • 在异步任务中,回调函数是最原始的解决方案,能实现异步任务的顺序执行。
  • 回调函数的本质是:"任务完成后应该做什么"
  • 回调容易造成代码嵌套深、可读性差的问题,即回调地狱。
  • 在现代开发中,更推荐使用 Promiseasync/await 来管理异步流程。

同步和异步是刚需,是我们在开发过程中必须要用的,目前我们只介绍了回调函数 这一种变异步为同步的方法,下一期我们将介绍Promise,async/await这两种更加好用的方法,来解决问题。

相关推荐
金金金__3 分钟前
优化前端性能必读:浏览器渲染流程原理全揭秘
前端·浏览器
Data_Adventure7 分钟前
Vue 3 手机外观组件库
前端·github copilot
泯泷13 分钟前
Tiptap 深度教程(二):构建你的第一个编辑器
前端·架构·typescript
屁__啦19 分钟前
前端错误-null结构
前端
lichenyang45319 分钟前
从0开始的中后台管理系统-5(userList动态展示以及上传图片和弹出创建用户表单)
前端
未来之窗软件服务24 分钟前
解析 div 禁止换行与滚动条组合-CSS运用
前端·css
Java中文社群44 分钟前
快看!百度提前批的面试难度,你能拿下吗?
java·后端·面试
不远处的小阿秋44 分钟前
2025年,前端还需要虚拟DOM吗
前端
DcTbnk1 小时前
tailwindcss、postcss、autoprefixer,这三个分别是干嘛的
前端
zReadonly1 小时前
antdv@4.x在360极速浏览器兼容解决办法
前端