最近开始不断学习实践 JavaScript ,出于性能测试的敏感,首先研究了 JavaScript 的异步编程实践,目前看跟之前学过的 Java 和 Go 都不太一样。使用的语法上相对更加复杂,也可能因为我实践得还太少。
异步编程
异步编程是一种在程序执行过程中,不阻塞主线程的任务处理方式。相较于同步编程,异步编程允许程序在等待某个任务(例如网络请求或文件读写)完成的同时,继续执行其他操作。这种方式极大提高了程序的效率,尤其在处理大量 I/O 操作时,表现尤为突出。
在 JavaScript 中,异步编程尤为重要,原因在于它的单线程特性。JavaScript 在浏览器环境下运行时,只有一个主线程负责执行代码。如果程序中某个任务需要较长时间才能完成(比如网络请求、数据库查询等),而没有采用异步处理方式,主线程将会被阻塞,导致整个页面的交互变得迟缓甚至无响应。
为了避免这种情况,JavaScript 提供了多种异步处理方式,如回调函数、Promise、async/await等。通过这些机制,JavaScript 能够在处理耗时任务时,不阻塞主线程,保持页面的流畅性和响应性。
回调函数(Callback)
回调函数是指作为参数传递给另一个函数,并在该函数执行完毕后调用的函数。在 JavaScript 的异步编程中,回调函数是最早且最基础的实现方式之一。当某个异步操作(如网络请求或定时器)完成时,JavaScript 运行时环境会调用提供的回调函数,继续执行后续的逻辑。这种模式允许我们在异步任务完成后进行处理,而不阻塞主线程。
在 JavaScript 中,回调函数通过结合浏览器或 Node.js 的事件循环机制来实现异步行为。单单使用 JavaScript 本身无法实现异步,而是通过将任务交给浏览器或 Node.js 的运行时(如定时器、I/O 操作、网络请求等)来处理,等这些任务完成后,再通过回调函数把结果传回给 JavaScript 主线程继续执行。这整个过程就是实现异步的关键。
示例
function fetchData(callback) {
console.log("Fetching data...");
setTimeout(() => {// 模拟耗时操作
// 模拟耗时操作
const data = "Hello FunTester!";
callback(data); // 调用回调函数,传递数据
}, 1000);
}
function processData(data) {
console.log("Data received:", data);// 处理数据的逻辑
}
console.info("start --------");
fetchData(processData);// 调用 fetchData 函数,传入回调函数
console.info("end --------");
在这个例子中,fetchData
模拟了一个异步数据获取的操作,执行完成后调用回调函数 processData
来处理获取到的数据。控制台打印:
start --------
Fetching data...
end --------
Data received: Hello FunTester!
Promise
Promise 是一种用于处理异步操作的对象,它代表了一个异步操作的最终完成(或失败)及其结果值。Promise 提供了更清晰、更可读的方式来管理多个异步操作,特别是当它们相互依赖时。相比回调函数,Promise 能避免回调地狱,使代码结构更加扁平化和易于维护。
Promise 的三种状态
- Pending(待定):初始状态,表示异步操作尚未完成,也没有结果。
- Fulfilled(已完成):异步操作成功完成,并返回一个值。
- Rejected(已拒绝):异步操作失败,并返回一个原因。
在 Promise 的生命周期中,它只能从 pending
状态转移到 fulfilled
或 rejected
状态,并且一旦状态发生变化,结果就不会再改变。
Promise 的基本用法
一个 Promise 对象会接受一个函数作为参数,该函数本身又有两个参数:resolve
和 reject
。当异步操作成功时,调用 resolve
来将 Promise 状态改为 fulfilled
;当操作失败时,调用 reject
将状态改为 rejected
。
.then()
和 .catch()
的用法
-
.then()
:用于处理 Promise 成功完成(fulfilled
)后的结果。你可以在.then()
中执行后续操作,链式调用。 -
.catch()
:用于捕获 Promise 的错误(rejected
),并处理异常情况。
Promise 示例
const fetchData = async () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = true; // 模拟成功或失败
if (success) {// 成功, 调用 resolve, 并传递数据
resolve("hello, FunTester");
} else {// 失败, 调用 reject, 并传递错误信息
reject("sorry, error occurred");
}
});
});
};
console.info("start--------------");
// 使用 Promise 来处理异步操作
fetchData()
.then((data) => {// 成功时的处理
console.log("received:", data);
return data.length;
})
.then((age) => {// 成功时的处理
console.log("length:", age);
})
.catch((error) => {// 失败时的处理
console.error("Error:", error);
});
console.info("end--------------");
控制台打印:
start--------------
end--------------
received: hello, FunTester
length: 16
下面是一些小小的 tips:
- Promise 提供了一种更加简洁、优雅的方式来处理异步操作,避免回调地狱。
- 它有三种状态:
pending
(待定)、fulfilled
(已完成)、rejected
(已拒绝)。 - 使用
.then()
处理成功结果,使用.catch()
捕获和处理错误。 - 多个异步操作可以链式调用,提升了代码的可读性和维护性。
async
/await
async
/await
是 ES2017(ES8)引入的语法,提供了一种更加简洁的方式来处理异步代码,使其看起来更像同步代码。这种方式可以减少 Promise 链的复杂性,提升代码的可读性和维护性。async
/await
实际上是基于 Promises 之上的语法糖。
通过使用 async
和 await
,我们可以编写看起来像同步执行的代码,但它在内部仍然是异步的。这种写法可以避免 Promise 链式调用的繁琐结构,将异步任务的执行流顺序化,逻辑更加清晰。
async
/await
的基本用法
-
async
函数:声明一个async
函数,它会自动返回一个 Promise。即使没有显式返回 Promise,async
函数默认会把返回值封装成一个 Promise。 -
await
表达式:用于等待一个 Promise 的结果。它暂停async
函数的执行,直到 Promise 解决(resolve)或拒绝(reject),然后继续执行后续代码。
示例:使用 async
/await
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {// 模拟异步操作
const success = true;
if (success) {
resolve("hello funtester");
} else {
reject("sorry, error");
}
}, 1000);
});
};
async function getData() {
try {
const data = await fetchData(); // 等待 Promise 解决
console.log("Data received:", data);
} catch (error) {
console.error("Error:", error); // 处理错误
}
}
console.info("start----------------");
let data = getData();
console.log("data:", data);
console.info("end----------------");
控制台打印:
start----------------
data: Promise { <pending> }
end----------------
Data received: hello funtester
在使用 async/await
时,错误处理的方式类似于同步代码中捕获异常。我们可以使用 try/catch
块来处理异步操作中的异常,这种方式比在每个 .then()
末尾添加 .catch()
更加直观。
async/await
基于 Promise
需要注意的是,async
/await
并不是替代 Promise 的机制,而是基于 Promise 进行的更高级抽象。它们简化了 Promise 的链式调用,消除了嵌套结构。可以把 await
视作一个暂停点,等待 Promise 完成(无论是成功还是失败),使得异步代码的处理更符合程序的执行逻辑。
异步编程的重要性
在 JavaScript 中,异步编程至关重要,因为 JavaScript 运行在单线程环境中,尤其是在浏览器和 Node.js 等平台中。单线程意味着同一时间只能执行一个任务。如果代码中的某些操作(如网络请求、文件读取或定时任务)需要较长的执行时间,而 JavaScript 只支持同步编程的话,整个线程将被阻塞,用户界面会变得卡顿或无响应。因此,异步编程通过不阻塞主线程来解决这个问题,使得 JavaScript 能够高效处理 I/O 密集型任务,保持应用的流畅运行。
当然 JavaScript 异步编程的内容还有其他高级用法,比如学习 Promise.all、Promise.race 等,将来等我实践充足再来分享。
FunTester 原创精华