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 stepconst 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 herecallback(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 Promisify或Denodeify (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/

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

  1. 回调函数与回调地狱及其解决方法 | JavaScript

    JavaScript中的回调函数与回调地狱及其解决方法 以下为整理的思维导图 图片版+文字版 文末附有参考文章链接 知识点思维导图版 一.什么是回调函数 1.MDN的定义 回调函数是作为参数传给另一个 ...

  2. 什么是回调函数?回调函数有什么缺点?如何解决回调地狱问题?

    什么是回调函数?回调函数有什么缺点?如何解决回调地狱问题? 参考文章: (1)什么是回调函数?回调函数有什么缺点?如何解决回调地狱问题? (2)https://www.cnblogs.com/kzxi ...

  3. 什么是地狱回调?解决回调地狱的两种方法

    地狱回调概念:回调函数套回调函数的情况就叫做回调地狱, //地狱回调setTimeout(function () { //第一层console.log('武林要以和为贵');setTimeout(fu ...

  4. 【Android 高性能音频】AAudio 音频流 数据回调细节 ( 数据回调函数优先级 | 数据回调函数 | 采样率 | 采样数 | 缓冲区调整 | 线程不安全 )

    文章目录 I . 数据回调函数优先级 II . 数据回调函数 相关内容 III . 采样率 处理细节 IV . 数据回调函数 每次 采样个数 numFrames V . 数据回调函数 缓冲区 ( AA ...

  5. 【Android 高性能音频】AAudio 音频流 PCM 采样 的 采样 缓冲 播放 的 连续机制 ( 数据回调机制 | 数据回调函数指针 | 实现数据回调函数 | 设置数据回调函数 )

    文章目录 I . AAudio 音频流 采样 缓冲 播放 的连续机制 II . AAudio 音频流 数据回调函数 函数指针类型定义 III . AAudio 音频流 数据回调函数 实现 IV . A ...

  6. 怎么理解回调函数? 回调函数合集

    网上查了一通,有点体会,特来分享与讨论. ******************************************************************************* ...

  7. java回调函数_PHP回调函数及匿名函数概念与用法详解

    1.回调函数 PHP的回调函数其实和C.Java等语言的回调函数的作用是一模一样的,都是在主线程执行的过程中,突然跳去执行设置的回调函数: 回调函数执行完毕之后,再回到主线程处理接下来的流程 而在ph ...

  8. reactjs组件实例三大属性之refs使用示例:字符串形式的ref、回调函数形式的ref、回调ref中回调执行次数的问题

    1_字符串形式的ref <!DOCTYPE html> <html lang="en"> <head><meta charset=&quo ...

  9. java 自定义函数的调用_Java/Android中的函数调用回调函数自定义回调函数

    在做Android自定义控件时遇到要自定义回调函数的问题,想想自己还暂时没有那么精深的技术,赶紧返过头回来再重新研究Java中回调函数的问题.然而不幸的是,网上太多杂乱的帖子和博客都是转来转去,而且都 ...

  10. python asyncio回调函数_python回调函数用法实例分析

    python回调函数用法实例分析 本文实例讲述了python回调函数用法.分享给大家供大家参考.具体分析如下: 软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用.回调和异步 ...

最新文章

  1. feignclient多个配置_@FeignClient同一个name使用多个配置类的解决方案
  2. Android 进程常驻(使用第三方MarsDaemon)(虽然不可用,但是还是保留下。)
  3. 计算机考试八页PPT,第八章节授课ppt-江苏省计算机等级考试.ppt
  4. weblogic.jdbc.wrapper.Blob_oracle_sql_BLOB cannot be cast to oracle.sql.BLOB 解决方法
  5. 【笔记】基于边缘检测和BP神经网络的大豆杂草识别研究
  6. (提示)ubuntu16.04通过sealos安装k8s,需要重新部署apply一下calico组件
  7. 初中 昆虫记思维导图_初中物理各单元思维导图,帮孩子扫清初中所有物理知识点!(附电子版)...
  8. 面经——Linux相关
  9. mysql快速随机_MySQL随机取数据最高效的方法
  10. abstract class和interface有什么区别
  11. 2018.4.23 数据结构
  12. c语言银行家算法模拟程序,C语言实现 操作系统 银行家算法
  13. 序列中连续值之间的差值列表
  14. pkill -kill -t pts/1
  15. GMT5SAR--由*.grd文件生成*.ps
  16. 解锁图案-九宫格有多少种组合?安全吗?用程序来解答
  17. 微信小程序引用vant toast 提示 “未找到 van-toast 节点“
  18. [Unity3D] Unity3D连接安卓设备调试unity程序
  19. 财政部、税务总局:集成电路设计和软件企业免两年所得税
  20. Sigmoid Function

热门文章

  1. 控制台查看java版本信息没有反应
  2. python批量化修改文件名字(带窗口,可调整设置)
  3. Excel中IF函数和AND函数结合使用进行多条件判断
  4. 云服务器搭建Stable Diffusion Web UI 教程
  5. 地块尺度的山区耕地精准提取方法
  6. python的lxml库简介_lxml库
  7. Win11安装CUDA教程
  8. 江小白包装设计原型_江小白换包装了,这次大变样!
  9. 清华朱军团队开源首个基于Transformer的多模态扩散大模型
  10. 【2018年南海区甲组】拆除桥墩(remove)