发布于 2026-01-06 1 阅读
0

回调地狱或尝试捕捉地狱(恐怖之塔)

回调地狱或尝试捕捉地狱(恐怖之塔)

什么是“回调函数”?

回调函数通常用作另一个函数的参数。

接收回调函数的函数通常会从数据库中获取数据、发出 API 请求、下载文件,这通常需要一段时间。

假设从 API 获取一些数据,请求大约需要 2 秒钟才能完成。

现在,您可以选择等待 API 调用完成,然后再显示您的 UI。

或者,您可以显示其他所有内容,并在需要显示 API 数据的地方显示加载器。

在 API 函数中,我们传递某种“回调函数”,该函数会将加载器替换为实际数据,以便在收到 API 的响应后进行处理。

它使用数据调用回调函数,然后我们的回调函数替换加载器。

让我们看看实际效果:

function getDataFromAPI(callbackFunction) {
  fetchSomeData().then((data) => {
    callbackFunction(data);
  });
}

getDataFromAPI(function replaceLoaderWithData(data) {
  // your awesome logic to replace loader with data
});
Enter fullscreen mode Exit fullscreen mode

或者

// from w3schools
function myDisplayer(sum) {
  document.getElementById('demo').innerHTML = sum;
}

function myCalculator(num1, num2, myCallback) {
  let sum = num1 + num2;
  myCallback(sum);
}

myCalculator(5, 5, myDisplayer);
Enter fullscreen mode Exit fullscreen mode

好的,你已经知道了。我们今天不是要学习回调函数是什么。

什么是“回调地狱”?

如果你的应用程序逻辑不太复杂,少量回调函数似乎无伤大雅。
但随着项目需求的增加,你会很快发现自己堆积了多层嵌套的回调函数。

像这样:

getAreas(function (areas) {
  getTowns(function (towns) {
    getCities(function (cities) {
      getCountries(function (countries) {
        getContinents(function (continents) {
          getPlanets(function (planets) {
            getSolarSystems(function (solarSystems) {
              getGalaxies(function (galaxies) {
                // Welcome to the callback hell...
              });
            });
          });
        });
      });
    });
  });
});
Enter fullscreen mode Exit fullscreen mode

当然,我们可以使用 JavaScriptPromise并迁移到.then& .catch

getAreas().then(function (areas) {
  getTowns().then(function (towns) {
    getCities().then(function (cities) {
      getCountries().then(function (countries) {
        getContinents().then(function (continents) {
          getPlanets().then(function (planets) {
            getSolarSystems().then(function (solarSystems) {
              getGalaxies().then(function (galaxies) {
                // Welcome to the callback hell AGAIN...
              });
            });
          });
        });
      });
    });
  });
});
Enter fullscreen mode Exit fullscreen mode

恭喜!欢迎来到回调地狱。

回调地狱,又称厄运金字塔,是一个俚语,用来描述数量庞大的嵌套“if”语句或函数。

异步等待救援!

Async/await 就像天堂一样,因为它避免了回调地狱或厄运金字塔,而是以清晰的逐行格式编写异步代码。

上述代码修改为:

// assuming the environment supports direct async function
const areas = await getAreas();
const towns = await getTowns();
const cities = await getCities();
const countries = await getCountries();
const continents = await getContinents();
const planets = await getPlanets();
const solarSystems = await getSolarSystems();
const galaxies = await getGalaxies();

😳😲😳
// now this... looks awesome!!!
Enter fullscreen mode Exit fullscreen mode

但...

这很棒,直到出现错误处理问题,因为最终你会得到一个恐怖的 try-catch 代码塔!

你所有优美的单行代码都会神奇地扩展成至少五行代码……

// assuming the environment supports direct async function

try {
  const areas = await getAreas();
} catch (err) {
  // handleError(err)
}

try {
  const towns = await getTowns();
} catch (err) {
  // handleError(err)
}

try {
  const cities = await getCities();
} catch (err) {
  // handleError(err)
}

try {
  const countries = await getCountries();
} catch (err) {
  // handleError(err)
}

// ... and so on.
Enter fullscreen mode Exit fullscreen mode

你可以找到一种简单的方法,那就是在每个 Promise 的末尾添加 catch 方法。

// assuming the environment supports direct async function
const areas = await getAreas().catch((err) => handleError(err));
const towns = await getTowns().catch((err) => handleError(err));
const cities = await getCities().catch((err) => handleError(err));
const countries = await getCountries().catch((err) => handleError(err));
const continents = await getContinents().catch((err) => handleError(err));
const planets = await getPlanets().catch((err) => handleError(err));
const solarSystems = await getSolarSystems().catch((err) => handleError(err));
const galaxies = await getGalaxies().catch((err) => handleError(err));
Enter fullscreen mode Exit fullscreen mode

这样看起来好多了,但是!还是有点重复。

另一个更好的选择是创建标准化的错误处理函数

该函数会先解析 Promise,然后返回一个数组。

数组中的第一个元素是数据,第二个元素是错误信息。

如果出现错误,则数据为空,并且错误信息定义如下:

async function promiseResolver(promise) {
  try {
    const data = await promise();
    return [data, null];
  } catch (err) {
    return [null, err];
  }
}
Enter fullscreen mode Exit fullscreen mode

现在,当你在代码中调用此函数时,你可以对其进行解构,从而得到一个简洁的单行代码并进行错误处理;
或者,如果你想对错误执行其他操作,可以使用常规的 if 语句。

你的主函数大概会是这样:

// assuming the environment supports direct async function
const [areas, areasErr] = await promiseResolver(getAreas);
const [towns, townsErr] = await promiseResolver(getTowns);
const [cities, citiesErr] = await promiseResolver(getCities);

if (citiesErr) {
  // do something
}

const [countries, countriesErr] = await promiseResolver(getCountries);
const [continents, continentsErr] = await promiseResolver(getContinents);
const [planets, planetsErr] = await promiseResolver(getPlanets);
const [solarSystems, solarSystemsErr] = await promiseResolver(getSolarSystems);
const [galaxies, galaxiesErr] = await promiseResolver(getGalaxies);

if (galaxiesErr) {
  // do something
}

// ... and so on.
Enter fullscreen mode Exit fullscreen mode

就这些啦!希望对你们有帮助,下次再见😉

文章来源:https://dev.to/ovi/callback-hell-or-try-catch-hell-tower-of-terror-5h78