JS 打工记:同步搬砖、异步摸鱼,Promise 来救场

前言

  在JavaScript开发中,异步是重中之重,也是前端入门阶段最难理解的知识点之一。从基础的同步/异步区别,到JS单线程底层原理,再到最基础的回调函数,都是我们进阶学习Promise的基石。本文结合通俗案例,拆解JS异步的底层逻辑与回调相关知识,帮大家从零吃透相关知识点。

1. 同步与异步

1.1 同步

  代码按照一个从上到下依次执行,这就是同步执行。

  写一段代码解释:

js 复制代码
console.log('第一个执行')
console.log('第二个执行')
console.log('第三个执行')
console.log('第四个执行')

  正如我们所想的一样,他会按上到下顺序执行:

1.2 异步

  异步就是在执行的过程中,如果碰到一个耗时的任务,就分精力出来执行另外一个任务。

举个生活中的例子:

  假设小明 7 点起床,前往学校需要 20 分钟,手头还有多项家务:煮饭 20 分钟、烧水 10 分钟、洗衣服 10 分钟、洗漱 5 分钟、晾衣服 3 分钟。想要省时不迟到,最优安排就是先启动煮饭和烧水这两项耗时较长的事,在它们运行的间隙,同步完成洗衣、洗漱等其他工作。这和代码里的异步原理完全一致,面对耗时操作时,合理穿插执行其他任务,提升整体效率。

  用代码让你加深异步理解:

js 复制代码
let a = 1;           //同步代码 或者叫同步任务
setTimeout(() => {   //异步任务,应该被挂起
    a = 2;
}, 1000);
console.log(a);

  此时输出会打印 1 还是 2 呢?知道了异步的概念后,我们应该就知道打印的a应该是 1 :

2. 单线程原理

  TS 默认是单线程执行的

 2.1 进程与线程

  进程:CPU从接收到一个指令,到加载完上下文所需要的时间。

  线程:CPU执行指令所需要的时间。

  举个例子方便你的理解:

  当我们新开一个浏览器的 tab 页这其实就是一个进程,而这个进程是由多个线程通力合作完成的(如HTTP 网络线程, 页面渲染线程)。

  启用 node 来运行 js ,这就是一个进程,但是 v8 会默认开启一个线程来执行代码,所以js代码在遇到一个耗时的任务时,就会将它挂起,先去执行那些不耗时的任务。

3. 回调

  我们知道,JavaScript 在执行代码时,遇到耗时任务会将其挂起,优先执行不耗时的同步任务。  

  这里就会出现一个很典型的问题:假设 A 函数是向后端请求数据的耗时任务,B 函数需要拿到 A 请求到的数据才能渲染页面。我们按正常顺序先调用 A、再调用 B,由于 JS 是单线程异步执行,引擎会先执行不耗时的 B 函数,再回头处理 A 的数据请求。  

js 复制代码
let a = null
function A() {
  setTimeout(() => {
    a = 100
  }, 1000)
}
function B() {
  console.log(a);
}
A()
B()

  这就会导致 B 拿不到数据,页面渲染失败,显然不是我们想要的结果。  那该怎么解决呢?其实思路很简单:让 B 函数等 A 函数请求完成后再执行 。最直接的方式,就是把 B 函数写到 A 函数的内部,等 A 拿到数据后再触发 B。这种 "把一个函数作为参数传给另一个函数,在合适的时机才执行" 的机制,就是回调。早期的 JavaScript 开发者,正是用这种回调方式来处理异步依赖问题。如以下代码:

js 复制代码
let a = null
function A() {
  setTimeout(() => {
    a = 100
    B()
  }, 1000)
  
}
function B() {
  console.log(a);
}
A()

 3.1 基本概念

  定义:当 B 函数需要依赖异步 A 的结果, 我们将 B 函数的调用放在 A 里面。

 3.2 回调地狱

  当 A 函数依赖 B 函数的结果,C 函数又依赖 A 函数,D 函数再依赖 C 函数...... 像这样层层依赖、一直嵌套到几十上百个函数时,代码会不断向右缩进,变成一团缠绕不清的结构。不仅可读性极差,出了问题也很难定位,维护成本极高,这就是典型的回调地狱

 3.3 Promise

  为了避免回调地狱,JS官方引入了Promise;Promise 就是一个 "承诺" :它接收一个异步任务,向你保证 ------等任务做完了,我再告诉你结果是成功还是失败

  接下来,我就详细讲讲它的具体用法:

js 复制代码
function xq() {
    // Promise 内部拥有一个状态 statue = pending
    return new Promise((resolve,reject) => {
        setTimeout(() => {
            console.log('刘局相亲成功');
            resolve()
        }, 2000);
    })
}
function marry() {
    return new Promise((resolve,reject) => {
        setTimeout(() => {
             console.log('刘局结婚了');
             resolve()
            }, 1000);
    })
}
function baby(){
    console.log('小刘出生')
}
 xq().then(() => {
     marry().then(() => {
         baby();
     });
 });

  我们来看看刘局从相亲 -> 结婚 -> 生娃的一个过程,很明显这是一个相亲和结婚是异步环节,只有先相亲,再结婚,最后再生娃,相比于之前那种老套的回调,Promise提供了更为简洁的、可读性更强方法。

  为了让代码不会一直向右推进,我们可以这样改:

js 复制代码
xq()
.then(() => {
    return marry();
})
.then(() => {
    baby();
})

  很明显,这样的写法让代码更加扁平化、结构更清晰,从上到下依次执行,不会出现层层嵌套的情况,可读性和维护性都大大提升。

  下面我们结合这个例子,深入看看 Promise 的底层运行原理

js 复制代码
function Promise(fn) {
  this.state = 'pending'  // 准备状态
  this.arr = [foo]

  const resolve = (res) => {
    this.state = 'resolved'  // 成功状态
    // foo(res)
  }
  const reject = () => {}

  fn(resolve, reject)
}

new Promise((resolve, reject) => {
  resolve()
})

执行步骤:

  1. new Promise() 得到了一个状态为 pending 的对象 。
  2. then(foo) then 会将 foo 存起来。
  3. 时间到达,resolve 被触发。
  4. 将 Promise 内部的状态更改为 成功(resolved), 将当初 then 存起来的 foo 函数也触发。

  Promise 还提供了 .catch() 方法,专门用来捕获异步任务执行失败时的错误信息。

js 复制代码
.then((res) => {
    console.log(res); 
})
.catch((err) => {
    console.log(err); // 异步任务失败时,会执行这里的代码
});

  这里的 reserr 并不是凭空产生的,它们的值是我们在创建 Promise 时,通过 resolve('Yes')reject('No') 主动传递出来的参数。

  简单来说,它们的对应关系是这样的:

js 复制代码
res = 'Yes';  // res 接收来自 resolve() 的数据
err = 'No';   // err 接收来自 reject() 的数据

4. 总结

  • 同步与异步:同步代码自上而下顺序执行;异步遇到耗时任务会将其挂起,优先执行其他代码,避免阻塞。

  • 进程与线程:进程是资源分配单位,线程是指令执行单位。JS 基于 V8 引擎单线程运行,这也是异步机制存在的根源。

  • 回调函数 :将后续逻辑放入异步任务内部,保障执行顺序;多层嵌套依赖会形成回调地狱,代码难读、难维护。

  • Promise :专为解决回调地狱而生的异步方案,拥有 pendingresolvedrejected 三种状态。通过 resolve 配合 .then() 处理成功逻辑,reject 配合 .catch() 捕获异常,支持链式调用,代码结构更简洁。

相关推荐
用户7138742290012 小时前
彻底搞懂浏览器客户端存储:从 localStorage 到完整存储体系
前端
bonechips12 小时前
告别 var,拥抱 let 和 const:JavaScript 变量声明完全指南
javascript·代码规范
如果超人不会飞12 小时前
别再自己套壳了!三分钟把你的浏览器变成 AI 的“提线木偶”——WebMCP 深度解析
javascript
掘金安东尼12 小时前
前端周刊第 467 期 🗂 本期精选目录
前端
liyang_83012 小时前
邦芒支招:四步化解面试情绪问题
面试·职场和发展
qcx2312 小时前
【系统学AI】07 ReAct范式:从奠基之作到Reflexion/RAF的演进
前端·人工智能·react.js
无糖可可果12 小时前
从 Python List 到 LLM:一个开发者的 AI 学习之路
前端
一点一木12 小时前
Claude Opus 4.8 实测:AI 终于学会「承认自己不知道」了?
前端·人工智能·claude
克里斯前端12 小时前
SSE实践(1)
前端