如何处理嵌套的回调并避免“回调地狱”

JavaScript is a strange language. Once in a while, you have to deal with a callback that’s in another callback that’s in yet another callback.

JavaScript是一种奇怪的语言。 有时,您必须处理另一个回调中的另一个回调。

People affectionately call this pattern the callback hell.

人们将这种模式称为回调地狱

It kinda looks like this:

看起来像这样:

firstFunction(args, function() {
  secondFunction(args, function() {
    thirdFunction(args, function() {
      // And so on…
    });
  });
});

This is JavaScript for you. It’s mind-boggling to see nested callbacks, but I don’t think it’s a “hell”. The “hell” can be manageable if you know what to do with it.

这是适合您JavaScript。 嵌套的回调令人难以置信,但是我不认为这是一个“地狱”。 如果您知道该如何处理,则“地狱”可以控制。

在回调上 (On callbacks)

I assume you know what callbacks are if you’re reading this article. If you don’t, please read this article for an introduction to callbacks before continuing. There, we talk about what callbacks are and why you use them in JavaScript.

如果您正在阅读本文,我想您知道什么是回调。 如果不这样做,请在继续之前阅读本文以获取有关回调的介绍。 在那里,我们讨论什么是回调以及为什么要在JavaScript中使用它们。

回调地狱的解决方案 (Solutions to callback hell)

There are four solutions to callback hell:

回调地狱有四种解决方案:

  1. Write comments

    发表评论
  2. Split functions into smaller functions

    将功能拆分为较小的功能
  3. Using Promises

    使用承诺
  4. Using Async/await

    使用异步/等待

Before we dive into the solutions, let’s construct a callback hell together. Why? Because it’s too abstract to see firstFunction, secondFunction, and thirdFunction. We want to make it concrete.

在深入探讨解决方案之前,让我们一起构造一个回调地狱。 为什么? 因为太抽象了,所以看不到firstFunctionsecondFunctionthirdFunction 。 我们要使其具体化。

构造一个回调地狱 (Constructing a callback hell)

Let’s imagine we’re trying to make a burger. To make a burger, we need to go through the following steps:

假设我们正在尝试制作一个汉堡。 要制作汉堡,我们需要执行以下步骤:

  1. Get ingredients (we’re gonna assume it’s a beef burger)

    获取食材(我们将假定它是牛肉汉堡)
  2. Cook the beef

    煮牛肉
  3. Get burger buns

    获取汉堡包
  4. Put the cooked beef between the buns

    将煮熟的牛肉放在the头之间
  5. Serve the burger

    服务汉堡

If these steps are synchronous, you’ll be looking at a function that resembles this:

如果这些步骤是同步的,那么您将看到类似于以下的函数:

const makeBurger = () => {
  const beef = getBeef();
  const patty = cookBeef(beef);
  const buns = getBuns();
  const burger = putBeefBetweenBuns(buns, beef);
  return burger;
};

const burger = makeBurger();
serve(burger);

However, in our scenario, let’s say we can’t make the burger ourselves. We have to instruct a helper on the steps to make the burger. After we instruct the helper, we have to WAIT for the helper to finish before we begin the next step.

但是,在我们的情况下,我们不能自己制作汉堡。 我们必须指导助手制作汉堡的步骤。 指导助手之后,我们必须等待助手完成,然后再开始下一步。

If we want to wait for something in JavaScript, we need to use a callback. To make the burger, we have to get the beef first. We can only cook the beef after we get the beef.

如果我们要等待JavaScript中的某些内容,则需要使用回调。 要制作汉堡,我们必须先获得牛肉。 拿到牛肉后,我们才能煮牛肉。

const makeBurger = () => {
  getBeef(function(beef) {
    // We can only cook beef after we get it.
  });
};

To cook the beef, we need to pass beef into the cookBeef function. Otherwise, there’s nothing to cook! Then, we have to wait for the beef to get cooked.

要煮牛肉,我们需要将beef传递到cookBeef函数中。 否则,就没有饭可做! 然后,我们必须等待牛肉煮熟。

Once the beef gets cooked, we get buns.

牛肉煮熟后,我们就得到了s头。

const makeBurger = () => {
  getBeef(function(beef) {
    cookBeef(beef, function(cookedBeef) {
      getBuns(function(buns) {
        // Put patty in bun
      });
    });
  });
};

After we get the buns, we need to put the patty between the buns. This is where a burger gets formed.

拿到小圆面包后,我们需要将小馅饼放在小圆面包之间。 这就是汉堡形成的地方。

const makeBurger = () => {
  getBeef(function(beef) {
    cookBeef(beef, function(cookedBeef) {
      getBuns(function(buns) {
        putBeefBetweenBuns(buns, beef, function(burger) {
            // Serve the burger
        });
      });
    });
  });
};

Finally, we can serve the burger! But we can’t return burger from makeBurger because it’s asynchronous. We need to accept a callback to serve the burger.

最后,我们可以为您提供汉堡! 但是我们不能从makeBurger返回burger ,因为它是异步的。 我们需要接受回调来提供汉堡。

const makeBurger = nextStep => {
  getBeef(function (beef) {
    cookBeef(beef, function (cookedBeef) {
      getBuns(function (buns) {
        putBeefBetweenBuns(buns, beef, function(burger) {
          nextStep(burger)
        })
      })
    })
  })
}

// Make and serve the burger
makeBurger(function (burger) => {
  serve(burger)
})

(I had fun making this callback hell example ?).

(我很高兴制作这个回调地狱示例?)。

回调地狱的第一个解决方案:写评论 (First solution to callback hell: Write comments)

The makeBurger callback hell is simple to understand. We can read it. It just… doesn’t look nice.

makeBurger回调地狱很容易理解。 我们可以阅读。 只是...看起来不太好。

If you’re reading makeBurger for the first time, you may think “Why the hell do we need so many callbacks to make a burger? It doesn’t make sense!”.

如果您是第一次阅读makeBurger ,您可能会想:“为什么我们需要这么多的回调才能制作汉堡? 这没有道理!”。

In such a case, you’d want to leave comments to explain your code.

在这种情况下,您需要留下注释来解释您的代码。

// Makes a burger
// makeBurger contains four steps:
//   1. Get beef
//   2. Cook the beef
//   3. Get buns for the burger
//   4. Put the cooked beef between the buns
//   5. Serve the burger (from the callback)
// We use callbacks here because each step is asynchronous.
//   We have to wait for the helper to complete the one step
//   before we can start the next step

const makeBurger = nextStep => {
  getBeef(function(beef) {
    cookBeef(beef, function(cookedBeef) {
      getBuns(function(buns) {
        putBeefBetweenBuns(buns, beef, function(burger) {
          nextStep(burger);
        });
      });
    });
  });
};

Now, instead of thinking “wtf?!” when you see the callback hell, you get an understanding of why it has to be written this way.

现在,不要想“ wtf ?!” 当您看到回调地狱时,您将了解为什么必须以这种方式编写。

回调地狱的第二种解决方案:将回调分为不同的函数 (Second solution to callback hell: Split the callbacks into different functions)

Our callback hell example is already an example of this. Let me show you the step-by-step imperative code and you’ll see why.

我们的回调地狱示例已经是一个示例。 让我向您展示逐步的命令性代码,您会明白为什么。

For getBeef, our first callback, we have to go to the fridge to get the beef. There are two fridges in the kitchen. We need to go to the right fridge.

对于我们的第一个回调getBeef ,我们必须去冰箱拿牛肉。 厨房里有两个冰箱。 我们需要去正确的冰箱。

const getBeef = nextStep => {
  const fridge = leftFright;
  const beef = getBeefFromFridge(fridge);
  nextStep(beef);
};

To cook beef, we need to put the beef into an oven; turn the oven to 200 degrees, and wait for twenty minutes.

要煮牛肉,我们需要把牛肉放进烤箱。 将烤箱转到200度,然后等待二十分钟。

const cookBeef = (beef, nextStep) => {
  const workInProgress = putBeefinOven(beef);
  setTimeout(function() {
    nextStep(workInProgress);
  }, 1000 * 60 * 20);
};

Now imagine if you have to write each of these steps in makeBurger… you’ll probably faint from the sheer amount of code!

现在想象一下,如果您必须在makeBurger编写这些步骤的每makeBurger ……您可能会因大量代码而晕倒!

For a concrete example on splitting callbacks into smaller functions, you can read this small section in my callback article.

有关将回调拆分为较小函数的具体示例,您可以在我的回调文章中阅读这一小节

回调地狱的第三个解决方案:使用诺言 (Third solution to callback hell: Use promises)

I’m going to assume you know what promises are. If you don’t, please read this article.

我假设你知道什么是诺言。 如果您不这样做,请阅读本文

Promises can make callback hell much easier to manage. Instead of the nested code you see above, you’ll have this:

承诺可以使回调地狱更易于管理。 您将拥有以下代码,而不是上面看到的嵌套代码:

const makeBurger = () => {
  return getBeef()
    .then(beef => cookBeef(beef))
    .then(cookedBeef => getBuns(beef))
    .then(bunsAndBeef => putBeefBetweenBuns(bunsAndBeef));
};

// Make and serve burger
makeBurger().then(burger => serve(burger));

If you take advantage of the single-argument style with promises, you can tweak the above to this:

如果您利用带有保证的单参数样式,可以对以上内容进行调整:

const makeBurger = () => {
  return getBeef()
    .then(cookBeef)
    .then(getBuns)
    .then(putBeefBetweenBuns);
};

// Make and serve burger
makeBurger().then(serve);

Much easier to read and manage.

更容易阅读和管理。

But the question is how do you convert callback-based code into promise-based code.

但是问题是如何将基于回调的代码转换为基于承诺的代码。

将回调转换为Promise (Converting callbacks to promises)

To convert callbacks into promises, we need to create a new promise for each callback. We can resolve the promise when the callback is successful. Or we can reject the promise if the callback fails.

要将回调转换为promise,我们需要为每个回调创建一个新的promise。 回调成功后,我们可以resolve承诺。 或者如果回调失败,我们可以reject承诺。

const getBeefPromise = _ => {
  const fridge = leftFright;
  const beef = getBeefFromFridge(fridge);
  
  return new Promise((resolve, reject) => {
    if (beef) {
      resolve(beef);
    } else {
      reject(new Error(“No more beef!”));
    }
  });
};

const cookBeefPromise = beef => {
  const workInProgress = putBeefinOven(beef);
  
  return new Promise((resolve, reject) => {
    setTimeout(function() {
      resolve(workInProgress);
    }, 1000 * 60 * 20);
  });
};

In practice, callbacks would probably be written for you already. If you use Node, each function that contains a callback will have the same syntax:

实际上,回调可能已经为您编写了。 如果使用Node,则包含回调的每个函数将具有相同的语法:

  1. The callback would be the last argument

    回调将是最后一个参数
  2. The callback will always have two arguments. And these arguments are in the same order. (Error first, followed by whatever you’re interested in).

    回调将始终有两个参数。 这些参数的顺序相同。 (首先出错,然后是您感兴趣的所有内容)。
// The function that’s defined for you
const functionName = (arg1, arg2, callback) => {
  // Do stuff here
  callback(err, stuff);
};

// How you use the function
functionName(arg1, arg2, (err, stuff) => {
  if (err) {
  console.error(err);
  }
  // Do stuff
});

If your callback has the same syntax, you can use libraries like ES6 Promisify or Denodeify (de-node-ify) that callback into a promise. If you use Node v8.0 and above, you can use util.promisify.

如果您的回调具有相同的语法,则可以使用ES6 PromisifyDenodeify (de-node-ify)之类的将该回调转换为Promise的库。 如果您使用Node v8.0及更高版本,则可以使用util.promisify

All three of them work. You can choose any library to work with. There are slight nuances between each method, though. I’ll leave you to check their documentation for how-tos.

他们三个都工作。 您可以选择要使用的任何库。 但是,每种方法之间都存在细微差别。 我将让您检查他们的文档以了解操作方法。

回调地狱的第四个解决方案:使用异步函数 (Fourth solution to callback hell: Use asynchronous functions)

To use asynchronous functions, you need to know two things first:

要使用异步函数,您首先需要了解两件事:

  1. How to convert callbacks into promises (read above)

    如何将回调转换为Promise(如上)
  2. How to use asynchronous functions (read this if you need help).

    如何使用异步功能(如果需要帮助,请阅读本手册 )。

With asynchronous functions, you can write makeBurger as if it’s synchronous again!

使用异步功能,您可以像重新同步一样编写makeBurger

const makeBurger = async () => {
  const beef = await getBeef();
  const cookedBeef = await cookBeef(beef);
  const buns = await getBuns();
  const burger = await putBeefBetweenBuns(cookedBeef, buns);
  return burger;
};

// Make and serve burger
makeBurger().then(serve);

There’s one improvement we can make to the makeBurger here. You can probably get two helpers to getBuns and getBeef at the same time. This means you can await them both with Promise.all.

我们可以在这里对makeBurger进行改进。 您可能可以同时获得两个助手来getBunsgetBeef 。 这意味着您可以通过Promise.all await它们。

const makeBurger = async () => {
  const [beef, buns] = await Promise.all(getBeef, getBuns);
  const cookedBeef = await cookBeef(beef);
  const burger = await putBeefBetweenBuns(cookedBeef, buns);
  return burger;
};

// Make and serve burger
makeBurger().then(serve);

(Note: You can do the same with Promises… but the syntax isn’t as nice and as clear as async/await functions).

(注意:您可以对Promises进行同样的操作,但是语法不如异步/等待功能那么清晰)。

结语 (Wrapping up)

Callback hell isn’t as hellish as you think. There are four easy ways to manage callback hell:

回调地狱并不像您想象的那样地狱。 有四种简单的方法可以管理回调地狱:

  1. Write comments

    发表评论
  2. Split functions into smaller functions

    将功能拆分为较小的功能
  3. Using Promises

    使用承诺
  4. Using Async/await

    使用异步/等待

This article was originally posted on my blog.Sign up for my newsletter if you want more articles to help you become a better frontend developer.

本文最初发布在我的博客上 如果您想获得更多文章来帮助您成为更好的前端开发人员,请注册我的时事通讯

翻译自: https://www.freecodecamp.org/news/how-to-deal-with-nested-callbacks-and-avoid-callback-hell-1bc8dc4a2012/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值