写点什么

JavaScript 异步编程入门

作者:FunTester
  • 2024-10-12
    河北
  • 本文字数:3439 字

    阅读完需:约 11 分钟

最近开始不断学习实践 JavaScript ,出于性能测试的敏感,首先研究了 JavaScript 的异步编程实践,目前看跟之前学过的 Java 和 Go 都不太一样。使用的语法上相对更加复杂,也可能因为我实践得还太少。

异步编程

异步编程是一种在程序执行过程中,不阻塞主线程的任务处理方式。相较于同步编程,异步编程允许程序在等待某个任务(例如网络请求或文件读写)完成的同时,继续执行其他操作。这种方式极大提高了程序的效率,尤其在处理大量 I/O 操作时,表现尤为突出。


在 JavaScript 中,异步编程尤为重要,原因在于它的单线程特性。JavaScript 在浏览器环境下运行时,只有一个主线程负责执行代码。如果程序中某个任务需要较长时间才能完成(比如网络请求、数据库查询等),而没有采用异步处理方式,主线程将会被阻塞,导致整个页面的交互变得迟缓甚至无响应。


为了避免这种情况,JavaScript 提供了多种异步处理方式,如回调函数Promiseasync/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 的三种状态

  1. Pending(待定):初始状态,表示异步操作尚未完成,也没有结果。

  2. Fulfilled(已完成):异步操作成功完成,并返回一个值。

  3. Rejected(已拒绝):异步操作失败,并返回一个原因。


在 Promise 的生命周期中,它只能从 pending 状态转移到 fulfilledrejected 状态,并且一旦状态发生变化,结果就不会再改变。

Promise 的基本用法

一个 Promise 对象会接受一个函数作为参数,该函数本身又有两个参数:resolvereject。当异步操作成功时,调用 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, FunTesterlength: 16
复制代码


下面是一些小小的 tips:


  • Promise 提供了一种更加简洁、优雅的方式来处理异步操作,避免回调地狱。

  • 它有三种状态:pending(待定)、fulfilled(已完成)、rejected(已拒绝)。

  • 使用 .then() 处理成功结果,使用 .catch() 捕获和处理错误。

  • 多个异步操作可以链式调用,提升了代码的可读性和维护性。

async/await

async/await 是 ES2017(ES8)引入的语法,提供了一种更加简洁的方式来处理异步代码,使其看起来更像同步代码。这种方式可以减少 Promise 链的复杂性,提升代码的可读性和维护性。async/await 实际上是基于 Promises 之上的语法糖。


通过使用 asyncawait,我们可以编写看起来像同步执行的代码,但它在内部仍然是异步的。这种写法可以避免 Promise 链式调用的繁琐结构,将异步任务的执行流顺序化,逻辑更加清晰。

async/await 的基本用法

  1. async 函数:声明一个 async 函数,它会自动返回一个 Promise。即使没有显式返回 Promise,async 函数默认会把返回值封装成一个 Promise。

  2. 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 等,将来等我实践充足再来分享。

发布于: 刚刚阅读数: 3
用户头像

FunTester

关注

公众号:FunTester,800篇原创,欢迎关注 2020-10-20 加入

Fun·BUG挖掘机·性能征服者·头顶锅盖·Tester

评论

发布
暂无评论
JavaScript 异步编程入门_FunTester_InfoQ写作社区