Javascript回调异步操作示例教程

有没有想过网站如何在不堵塞所有内容的情况下获取数据?这就是异步操作的魔力!

回调是处理这些幕后任务的经典方法。想象一下,您告诉朋友(函数)获取某些东西(数据)。当你的朋友购物(操作运行)时,你(主程序)可以继续做事。一旦他们带回项目(数据),您的朋友就会告诉您(回调函数),您就可以使用它!

回调是一个简单的概念,但存在更新的方法来保持代码整洁。尽管如此,理解回调仍然是异步世界中伟大的第一步!

传统同步与异步操作
想象一下,您有一个名为 的函数makeCoffeeSync可以完美地冲泡一杯咖啡。但是,它是同步的,这意味着程序会等待整个过程完成后再继续。它可能如下所示:

function makeCoffeeSync() {
  // Simulate brewing time (waiting 5 seconds)
  for (let i = 0; i < 50000000; i++) {}
// This loop wastes time to simulate brewing
  console.log(
"Coffee is ready!");
}

makeCoffeeSync();
// Call the function

console.log(
"While the coffee brews, I can..."); // This line won't be reached until coffee is done!


在这种情况下

  • 程序会研磨咖啡豆
  • 加热水并冲泡咖啡,
  • 所有这些都在该makeCoffeeSync函数内进行。
  • 在咖啡喝完之前,甚至不会到达该队列,因为程序处于等待状态(for循环模拟冲泡时间)。

这对于网站来说并不理想;用户会看到等待的屏幕,直到咖啡准备好!

异步救世主:多任务处理的咖啡
现在,让我们看看异步操作如何在回调的帮助下以不同的方式处理咖啡制作:

function makeCoffeeAsync(callback) {
  // Simulate brewing time (still 5 seconds)
  setTimeout(() => {
    console.log(
"Coffee is ready!");
    callback();
// Call the callback function once brewing is done
  }, 5000);
}

makeCoffeeAsync(function() {
  console.log(
"... I can check my email while the coffee brews!");
});

console.log(
"I can also start reading the news..."); // This line can be executed immediately!

神奇之处就在这里:

  • makeCoffeeAsync 将一个回调函数作为参数。该回调函数会告诉 makeCoffeeAsync 函数在咖啡煮好后要运行哪些代码。
  • setTimeout 模拟冲煮时间(5 秒)。在 setTimeout 函数中,我们调用回调函数(console.log("咖啡煮好了!"))来提示咖啡已经煮好。
  • 一旦咖啡煮好(setTimeout 延迟后),我们就会执行所提供的回调函数(console.log("......我可以在煮咖啡时查看电子邮件了!")。
  • 重要的是,console.log("我还可以开始阅读新闻......")行可以立即执行,因为程序不会等待咖啡煮好。

这种异步方法允许程序在后台冲泡咖啡时保持平稳运行。回调功能可确保您在咖啡煮好后收到通知并采取行动(如查看电子邮件)。这使得用户体验更加灵敏!

回调优点
把异步函数想象成送货员。你(主程序)不能等着他们送包裹(异步操作的结果)。但你可以给他们下达指示(回调函数),告诉他们拿到包裹(操作完成)后该怎么做。

传递指令:

  • 异步函数:假设您有一个名为 fetchDataFromServerAsync 的函数,用于从服务器获取数据。这个函数是异步的,因为它需要时间与服务器通信。
  • 打包指令:调用此函数时,需要提供另一个函数(回调函数)作为参数。回调函数包含数据检索后的操作指令。

发送过程:
  • 异步函数启动:fetchDataFromServerAsync 函数开始与服务器联系并获取数据。
  • 程序无需等待:当 fetchDataFromServerAsync 忙碌时,主程序无需等待。它可以继续执行其他代码。
  • 传送完成!(操作完成):一旦从服务器检索到数据,fetchDataFromServerAsync 函数就完成了异步操作。
  • 移交数据包(回调调用):现在,fetchDataFromServerAsync 会记住你之前提供的回调函数。它会调用该回调函数,并将检索到的数据(数据包)作为参数传递。

function fetchDataFromServerAsync(callback) {
  // Simulate fetching data from a server (wait 2 seconds)
  setTimeout(() => {
    const catData =
"Here are some cat videos!";  // This is the retrieved data (package)
    callback(catData);  
// Call the callback function and pass the data
  }, 2000);
}

// The callback function to handle the retrieved data
function processCatData(data) {
  console.log(
"Great! I can now show these cat videos to the user:", data);
}

// Calling the asynchronous function and providing the callback
fetchDataFromServerAsync(processCatData);

console.log(
"While we wait for cat videos, I can...");  // This can be executed immediately

在这个例子中

  • fetchDataFromServerAsync 是异步函数,用于模拟从服务器获取数据(猫视频)。
  • processCatData 是回调函数,用于指定如何处理获取的数据(猫咪视频)--在本例中,将其记录到控制台。
  • 回调函数(processCatData)作为参数传递给 fetchDataFromServerAsync。
  • 主程序可以继续运行(console.log("当我们等待猫视频时,我可以...")),因为它不需要等待数据被获取。
  • 一旦 fetchDataFromServerAsync 完成数据(猫咪视频)的获取,它就会调用回调函数(processCatData)并将数据移交给主程序。
  • 这就是异步操作回调的精髓。回调函数可确保您在异步任务完成时对结果采取行动,从而保持程序的响应速度。

回调的优点和缺点
回调为处理异步操作提供了一些好处:

  • 简单性: 将函数作为参数传递的基本概念相对容易掌握,特别是对于初学者来说。
  • 灵活性: 回调可与各种异步任务一起使用,提供了一种通用的方法。

然而,随着应用程序复杂性的增加,回调可能会带来一些挑战:
  • 回调地狱: 当您开始在彼此之间嵌套多个回调时(就像送货员指令的连锁反应),代码可能会变得难以阅读和维护。想象一下给出一长串具有复杂依赖关系的指令 - 它可能会变得混乱!这种错综复杂的结构通常被称为“回调地狱”。
  • 可读性: 过多的嵌套会使逻辑流程难以理解。可能不清楚哪个回调函数负责它们的交互内容以及交互方式。
  • 错误处理: 处理回调链中的错误可能很麻烦。您需要在回调链的每个级别检查错误,从而使代码变得冗长且容易出错。

这是一个类比:想象一下,向多个传递包(回调)的朋友提供一长串具有复杂依赖关系的指令。如果传递出现问题,跟踪谁在做什么以及会发生什么可能会令人困惑。

虽然回调发挥着历史作用,但现代 JavaScript 提供了 Promises 和 Async/Await 等替代方法来解决这些可读性和可维护性问题。然而,理解回调为异步编程概念奠定了坚实的基础。

超越回调:Promise 和 Async/Await 实现更简洁的异步
虽然回调是一个基本概念,但在复杂的场景中它们可能会变得很麻烦。这是一个现实世界的例子:

1 想象一个电子商务网站:

  1. 回调连锁反应: 用户单击“添加到购物车”。这会触发回调以更新购物车显示。但是等等,购物车可能会根据用户的忠诚度计划应用折扣!这需要另一个异步调用来获取用户数据,然后是一个回调来计算折扣,以及另一个回调来更新购物车显示的最终价格。这种回调的连锁反应很快就会变得难以管理和调试。

2 Promise 和 Async/Await 的救援:
现代 JavaScript 为处理异步操作提供了更清晰的替代方案:

  • Promise:  Promise 为异步任务提供了一种更加结构化的方法。它们代表异步操作的最终完成(或失败),并提供一种以更可读的方式将操作链接在一起的方法。
  • Async/Await:  Async/Await 构建在 Promises 之上,允许您以类似于同步代码的方式编写异步代码。它使用 async and await 关键字使异步代码看起来更加同步,从而提高可读性。

3 Promise 和 Async/Await 的好处:

  • 提高可读性: 与回调地狱相比,基于 Promise 的代码或使用 Async/Await 的代码通常更易于阅读和维护。逻辑流程更加清晰,错误处理变得更加易于管理。
  • 更好的错误处理:  Promise 允许集中错误处理,从而更容易捕获和处理异步操作中的错误。
  • 更简洁的代码: 总体而言,这些方法可以生成更简洁、更易于维护的代码,尤其是在复杂的异步场景中。

虽然回调占有一席之地,但 Promises 和 Async/Await 提供了一种更强大、更高效的方法来处理现代 Web 开发中的异步操作。通过了解回调及其限制,您可以了解这些新技术在编写干净且可维护的异步代码方面的优势。