JavaScript 异步编程

在 JavaScript 中,代码默认是同步执行的,即按顺序逐行执行。不过,处理一些耗时操作时(如 API 请求或文件读取),同步代码可能会造成页面卡顿。这时,异步编程就派上用场了。

1. 回调函数

1.1 基本概念

回调函数是异步编程的最基础形式。它是一个函数,在任务完成后执行。将回调函数作为参数传递给另一个函数,使得任务完成时可以执行特定的代码。

1
2
3
4
5
6
7
8
9
10
function fetchData(callback) {
setTimeout(() => {
console.log("数据获取完毕");
callback();
}, 2000); // 模拟 2 秒的网络延迟
}

fetchData(() => {
console.log("处理数据...");
});

1.2 回调地狱

当任务依赖于多个异步操作时,回调函数会嵌套得越来越深,导致代码难以阅读和维护。这种现象被称为 回调地狱

1
2
3
4
5
6
7
8
9
fetchData(() => {
console.log("Step 1 complete");
fetchData(() => {
console.log("Step 2 complete");
fetchData(() => {
console.log("Step 3 complete");
});
});
});

2. Promise

2.1 什么是 Promise

Promise 是 ES6 引入的一种处理异步操作的对象。它有三种状态:

  • Pending(待定):初始状态,任务尚未完成。
  • Fulfilled(已完成):任务成功,返回结果。
  • Rejected(已拒绝):任务失败,返回错误。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let promise = new Promise((resolve, reject) => {
let success = true;
if (success) {
resolve("任务成功");
} else {
reject("任务失败");
}
});

promise.then(result => {
console.log(result); // "任务成功"
}).catch(error => {
console.log(error);
});

2.2 使用链式 Promise

可以使用 .then() 链式调用多个 Promise,从而避免回调地狱问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function step1() {
return new Promise((resolve) => {
setTimeout(() => {
console.log("Step 1 complete");
resolve();
}, 1000);
});
}

function step2() {
return new Promise((resolve) => {
setTimeout(() => {
console.log("Step 2 complete");
resolve();
}, 1000);
});
}

step1().then(step2).then(() => {
console.log("All steps complete");
});

2.3 Promise.all 和 Promise.race

  • Promise.all:等待所有 Promise 完成,返回一个包含所有结果的数组。
  • Promise.race:只要一个 Promise 完成,就返回该 Promise 的结果。
1
2
3
4
5
6
7
8
9
10
let p1 = new Promise((resolve) => setTimeout(resolve, 1000, "P1 done"));
let p2 = new Promise((resolve) => setTimeout(resolve, 2000, "P2 done"));

Promise.all([p1, p2]).then(results => {
console.log(results); // ["P1 done", "P2 done"]
});

Promise.race([p1, p2]).then(result => {
console.log(result); // "P1 done"
});

3. async/await

3.1 基本概念

async/await 是基于 Promise 的语法糖,让异步代码看起来更像同步代码。async 函数返回一个 Promise,而 await 关键字用于等待一个 Promise 完成,只有在 async 函数中才能使用 await

1
2
3
4
5
6
7
async function fetchData() {
let response = await fetch("https://api.example.com/data");
let data = await response.json();
console.log(data);
}

fetchData();

3.2 使用 async/await 处理多个异步任务

可以使用 await 来处理多个异步操作,代码看起来更加直观:

1
2
3
4
5
6
7
async function processSteps() {
await step1();
await step2();
console.log("All steps complete");
}

processSteps();

3.3 处理错误

async/await 中可以使用 try...catch 来捕获错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
async function fetchData() {
try {
let response = await fetch("https://api.example.com/data");
if (!response.ok) {
throw new Error("数据获取失败");
}
let data = await response.json();
console.log(data);
} catch (error) {
console.error(error.message);
}
}

fetchData();

4. JavaScript 中的事件循环

4.1 什么是事件循环

JavaScript 是单线程语言,它的异步操作通过 事件循环 来实现。事件循环的核心概念是任务队列(Task Queue)和调用栈(Call Stack)。当调用栈为空时,事件循环会从任务队列中取出任务并将其压入调用栈中执行。

4.2 微任务与宏任务

异步任务分为 宏任务微任务setTimeoutsetInterval 属于宏任务,而 Promise.thenprocess.nextTick 属于微任务。

在事件循环中,每个宏任务结束后,都会先检查并执行所有的微任务,才会进入下一个宏任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
console.log("start");

setTimeout(() => {
console.log("macro task");
}, 0);

Promise.resolve().then(() => {
console.log("micro task");
});

console.log("end");

// 输出顺序:
// "start"
// "end"
// "micro task"
// "macro task"

JavaScript 异步编程
https://blog.pangcy.cn/2020/05/05/前端编程相关/JavaScript/JavaScript 异步编程/
作者
子洋
发布于
2020年5月5日
许可协议