1 概述

回调函数就是作为一个函数的参数的函数,在外部函数执行完毕的时候,这个回调函数会在特定的时机执行。通常在同步或者异步的编程场景下要用到,异步编程的时候可以用promise 或者 async/await , 定时器setTimeout,这些时间相关的api。

回调地狱就是原生回调函数们不断嵌套嵌套嵌套,像俄罗斯套娃一样,虽然实现了按照一定顺序的输出,但是由于层层嵌套难以维护,不好调试和复用。

这个时候避免回调地狱用的就是promise .then ,看起来就是链式调用then ,然后在这个基础上有一个async / await 语法糖 , 写起来更简洁(看起来像同步编程的代码一样)。

这两种方式都避免了回调地狱,代码复用性和可读性更好。

2 回调函数

简单来说,回调函数就是一个被作为参数传递给另一个函数的函数,并且这个被传递的函数在外部函数执行完毕后的某个时机被“回调”执行。

回调函数是作为参数传递到另一个函数中,然后在外部函数内调用以完成某种例行程序或操作的函数。

JavaScript 回调函数 | 全栈开发

回调函数 - MDN Web 文档术语表:Web 相关术语的定义 | MDN 

你需要先定义 doSomething 函数,或者使用已有的异步API(如 setTimeoutPromise 等)才能正常运行这段代码。

Window:setTimeout() 方法 - Web API | MDN

如果你的 doSomething 是同步的:

let value = 1;

function doSomething(callback) {
  callback();  // 同步执行回调
}

doSomething(() => {
  value = 2;
});

console.log(value); // 输出: 2

如果你的 doSomething 是异步的(比如使用 setTimeout):

let value = 1;

function doSomething(callback) {
  setTimeout(callback, 0);  // 异步执行回调
}

doSomething(() => {
  value = 2;
});

console.log(value); // 输出: 1(因为回调函数还没有执行)

最常见的实际例子:

let value = 1;

// 模拟异步操作
setTimeout(() => {
  value = 2;
  console.log('回调中:', value); // 最后输出: 回调中: 2
}, 0);

console.log('当前:', value); // 先输出: 当前: 1

关键点:

  1. 同步代码立即执行,console.log 会在回调函数执行后执行

  2. 异步代码(如 setTimeoutPromisefetch 等)会将回调放入事件队列,等待主线程空闲时执行

  3. 在异步情况下,console.log 会在回调函数执行执行

3 回调地狱(Callback Hell)

一文告诉你什么是回调地狱,如何解决回调地狱?-CSDN博客

虽然回调函数是处理异步的基础,但在实际开发中,如果存在多个相互依赖的异步操作,就可能导致回调函数层层嵌套。每一层异步操作都需要在前一层操作的回调函数内部发起,形成所谓的“回调地狱”(Callback Hell)或“毁灭金字塔”(Pyramid of Doom)。

回调地狱呢?回调函数里面一直嵌套回调函数,类似于定时器里面一直嵌套setTimeout,如果需要执行很多轮呢?这么就是陷入了回调地狱,代码可读性很差,也不好维护 。

例子

定时器层层嵌套

// 回调地狱版本:定时器层层嵌套
setTimeout(() => {
  console.log('1秒后执行第1个任务');
  
  setTimeout(() => {
    console.log('再2秒后执行第2个任务');
    
    setTimeout(() => {
      console.log('再3秒后执行第3个任务');
      
      setTimeout(() => {
        console.log('再4秒后执行第4个任务');
        // ... 可以无限嵌套下去
      }, 4000);
      
    }, 3000);
    
  }, 2000);
  
}, 1000);

多个异步操作嵌套

// 用户注册流程的回调地狱
function registerUser(userData, callback) {
  validateUser(userData, (isValid) => {
    if (isValid) {
      checkEmailExists(userData.email, (exists) => {
        if (!exists) {
          createUser(userData, (userId) => {
            sendWelcomeEmail(userId, (emailSent) => {
              if (emailSent) {
                logActivity(userId, 'registered', (logged) => {
                  callback(null, { success: true, userId });
                });
              } else {
                callback('邮件发送失败');
              }
            });
          });
        } else {
          callback('邮箱已存在');
        }
      });
    } else {
      callback('数据验证失败');
    }
  });
}

解决办法:promise , then ,promise发出了以后,then依次执行,或者用async await 更简洁,好复用 。

// 伪代码示例回调地狱结构

asyncOperation1(data, function(result1) {

asyncOperation2(result1, function(result2) {

asyncOperation3(result2, function(result3) {

// ... 更多嵌套

console.log("最终结果: ", result3);

}, failureCallback);

}, failureCallback);

}, failureCallback);

这种代码结构可读性差,难以维护和调试。为了解决这个问题,JavaScript 社区发展出了更先进的异步处理方案。

回调函数的替代方案

随着 JavaScript 语言的发展,出现了更优雅地处理异步操作的方式,旨在解决回调地狱问题。其中最主要的是 Promises 和 Async/Await 语法。

Promises 提供了一种链式调用的方式来组织异步操作,使得代码结构更扁平化。

4 Async/Await

Async/Await 是建立在 Promises 之上的语法糖,它允许开发者用更接近同步代码的写法来处理异步逻辑,极大地提高了代码的可读性和可维护性。尽管如此,理解回调函数仍然是掌握这些高级概念的基础。

async function 声明创建一个绑定到给定名称的新异步函数。函数体内允许使用 await 关键字,这使得我们可以更简洁地编写基于 promise 的异步代码,并且避免了显式地配置 promise 链的需要。

理解异步函数async和await的用法_async await用法-CSDN博客

async function - JavaScript | MDN

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐