一、了解进程与线程
在不同场景中,进程都可以用来描述该场景中的一个效果,线程是进程里面的一个更小的单位,通常多个线程配合工作构成一个进程。
- 进程:操作系统资源分配的基本单位,每个进程拥有独立的内存空间和资源。一个进程可以有多个线程。比如在Windows系统中,一个运行的
xx.exe
就是一个进程。它是一个运行中的程序实例,负责完成特定任务。进程之间相互独立,切换时需要较大的系统开销。 - 线程:进程中的执行单元,CPU调度的基本单位。线程共享进程的内存和资源,但每个线程有自己的栈和程序计数器。线程切换的开销较小,因此被称为轻量级进程。
二、v8 引擎的异步执行机制
js 默认是单线程的语言,因为 js 设定是为了做浏览器的脚本语言,尽量少的开销用户设备的性能。因为 js 的这种执行规则,导致我们在开发过程中时而会出现代码异步的情况。
- 同步代码:按顺序执行,阻塞后续代码直至完成。
- 异步代码:被挂起并放入任务队列,待同步代码执行完毕后进入微任务/宏任务队列。
v8运行一份js代码,会创建一个进程,从上往下执行代码,遇到同步代码就直接执行,遇到异步代码就跳过,先去执行后面的同步代码,等到后面的同步代码全部执行完毕后,再回过头执行异步代码。
以相亲函数案例说明传统模式的缺陷:
1function date() {
2 setTimeout(() => {
3 console.log("相亲成功");
4 return true;
5 }, 1000);
6}
7
8function marry() {
9 console.log("结婚");
10}
11
12date();
13marry();
上述代码执行结果为结婚 相亲成功
,其根本原因是v8引擎跳过异步代码继续执行后续同步代码,导致逻辑顺序错乱。
三、回调函数:异步编程的基础
1. 什么是回调函数?
回调函数是指将一个函数作为参数传递给另一个函数,并在外部函数内部调用这个传入的函数。在异步编程中,回调函数通常用于处理异步操作完成后的结果。
1function fetchData(callback) {
2 console.log("开始获取数据...");
3 setTimeout(() => {
4 const data = "这里是从服务器获取的数据";
5 callback(data);
6 }, 2000);
7}
8
9function handleData(data) {
10 console.log("获取的数据:", data);
11}
12
13
14fetchData(handleData);
在这个示例中,fetchData
函数模拟了从服务器获取数据的过程,使用setTimeout
模拟网络延迟。callback
参数是我们传入的处理函数,当数据准备好后,通过callback(data)
调用这个处理逻辑。
2. 回调函数的问题
早期浏览器广泛支持回调函数,适合简单的异步操作。但当嵌套过深时,代码的可读性差,维护困难,排查问题困难,形成回调地狱。
回调地狱:多层嵌套导致代码可读性灾难。
1API1(() => {
2 API2(() => {
3 API3(() => {
4
5 });
6 });
7});
代码结构随异步层级增加呈”括号金字塔”形态,严重降低维护性。
四、Promise:异步编程的救星
1. Promise的基本概念
promise 是 es6 新增的一个语法,用来解决回调地狱的问题。promise 是一个构造函数,用来封装一个异步操作,并且可以获取到异步操作的结果。
promise 有三种状态: 待定态(pending)、兑现态(fulfilled)、拒绝态(rejected)。
-
pending:初始状态,既不是成功,也不是失败;
-
fulfilled:操作成功完成,通过
resolve
方法传递结果。 -
rejected:操作失败,通过
reject
方法传递错误信息。
promise 状态的改变只有两种: 从待定态到兑现态或者从待定态到拒绝态。一旦状态变为 fulfilled
或 rejected
,就不会再变(不可逆性)。
1function fetchData() {
2 return new Promise((resolve, reject) => {
3 console.log("开始获取数据...");
4 setTimeout(() => {
5 const data = "这里是从服务器获取的数据";
6 resolve(data);
7
8 }, 2000);
9 });
10}
11
12
13
14
15
16fetchData()
17 .then((data) => {
18 console.log("获取的数据:", data);
19 })
20 .catch((error) => {
21 console.error("发生错误:", error);
22 });
在这个实例中,fetchData
返回一个Promise对象。在Promise内部,我们仍然使用setTimeout
模拟异步操作。数据成功时调用resolve(data)
,将结果传递给.then()
中的方法;如果发生错误,则调用reject(error)
并通过.catch()
捕获。
Promise.then()
方法返回一个新的 Promise
实例,从而支持链式调用。如果回调函数返回一个值,新Promise将以该值为结果resolve
。
2. Promise的核心优势
特性 | 传统回调 | Promise机制 |
---|---|---|
代码结构 | 嵌套层级加深 | 链式调用(.then().then()) |
错误处理 | 分散在各回调中 | 统一错误捕获(.catch()) |
并行操作 | 手动协调 | Promise.all()统一管理 |
状态追踪 | 黑箱操作 | 明确的pending/resolved状态 |
五、展望:从Promise到async/await
async
、await
本质上是生成器(Generator)与Promise的组合封装。
async 函数:用 async 声明的函数会自动返回一个 Promise 对象。无论函数内部返回的是否是Promise,都会被包装成 Promise。
1async function example() {
2 return 42;
3}
await 表达式:await 会暂停当前 async 函数的执行,等待后面的Promise完成。如果是非Promise值,会使用Promise.resolve()包装。
1async function foo() {
2 const result = await somePromise;
3 return result;
4}
相比Promise链式调用,async/await使异步代码呈现同步代码的线性结构,消除了回调地狱问题。
1
2fetchData()
3 .then(data => processData(data))
4 .then(result => displayResult(result))
5 .catch(error => handleError(error));
6
7
8async function handleData() {
9 try {
10 const data = await fetchData();
11 const result = await processData(data);
12 displayResult(result);
13 } catch (error) {
14 handleError(error);
15 }
16}