博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
How to deal with nested callbacks and avoid “callback hell”(翻译)
阅读量:6094 次
发布时间:2019-06-20

本文共 6051 字,大约阅读时间需要 20 分钟。

(渣渣小怪兽翻译,如果看翻译不开心可以看->)

js是一门强大语言。有时候,你必须处理另一个回调中的回调,而这个回调这是另一个回调中的回调。

人们形象的描述这种模式为回调地狱

它有点像这样:

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

这是js中的回调。当你看到这样的嵌套回调, 它可能令你的大脑难以置信,但我不认为这是“地狱”.这个“地狱”可以更好的管理,如果你知道怎么处理它的话。

关于回调

我假设当你在阅读这篇文章的时候你知道回调的概念.如果你不知道, 请阅读这篇, 在我们继续往下走之前这篇文章会介绍什么是回调。在那里,我们讨论回调是什么以及为什么在JavaScript中使用它们。

回调的处理方案

这是四个处理回调地狱的方案

  1. 写注释
  2. 拆分函数成为更小的函数
  3. 使用Promise
  4. 使用Async/await

在我们拆分讲解这个解决方案之前, 让我们一起构造一个回调地狱. 为什么?因为它真的太抽象了,当我们看到上面的例子: firstFunction, secondFunciton 和 thirdFunction.我们让构造一个真是的回调, 让我们的例子更具体。

构造一个回调地狱

让我们想象, 现在我们要做一个汉堡.为了制作一个汉堡, 我们需要执行以下步骤来达到目的:

  1. 获取配料
  2. 烹饪牛肉
  3. 获取汉堡的面包
  4. 把煮好的牛肉放在面包之间
  5. 供应汉堡

如果这些步骤都是同步的, 你将看到一个函数像下面这样:

const makeBurger = () => {  const beef = getBeef();  const patty = cookBeef(beef);  const buns = getBuns();  const burger = putBeefBetweenBuns(buns, beef);  return burger;};const burger = makeBurger();serve(burger);复制代码

然而, 在我们的步骤中,我们不能自己制作汉堡。我们必须指导助手制作汉堡的步骤。在我们指示助手之后,我们必须等待助手完成步骤,然后才开始下一步。

如果我们想在js中等待一会再执行, 我们就需要使用回调。为了制作汉堡, 我们不得不先获取牛肉。我们只能在获取牛肉后再煮牛肉。

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

为了煮牛肉, 我们需要传递牛肉进入cookBeef函数内.否则我们将没有东西煮。因此我们不得不等待牛肉煮熟.

一旦牛肉煮好了,我们就要获取面包

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

在我们获取面包后,我们需要放肉饼在两个面包之间。这就是汉堡形成的地方.

const makeBurger = () => {  getBeef(function(beef) {    cookBeef(beef, function(cookedBeef) {      getBuns(function(buns) {        putBeefBetweenBuns(buns, beef, function(burger) {            // 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 burgermakeBurger(function (burger) => {  serve(burger)})复制代码

制作这个回调的例子很有趣啊

解决方案1: 写注释

这个makeBurger回调是简单易于理解。我们可以读懂。它只是有一些不好看。

如果你第一次读到makeBurger这个函数, 你可能在想“为什么我们需要这么多回调才能制作汉堡呢?这没有意义!”

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

// 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);        });      });    });  });};复制代码

现在,不会是在想“WTF”, 当你看到这个回调地狱, 你可以理解为什么要用这个方式去写它了。

解决方案2:将回调拆分为不同的函数

我们的回调地狱的例子已经是一个很棒拆分的例子。让我给你一步步的拆分代码, 就会明白为什么我这样说了。

拿getBeef, 我们的第一个回调来说, 我们为了拿到牛肉不得不先去冰箱。厨房里有两个冰箱,我们需要去右手边的冰箱拿牛肉。

const getBeef = nextStep => {  const fridge = leftFright;  const beef = getBeefFromFridge(fridge);  nextStep(beef);};复制代码

为了烹饪牛肉,我们需要把牛肉放进烤箱里, 把烤箱开到200度, 并且等20分钟

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

现在想象一下, 如果你不得不写每个步骤在makeBurger ,那么这个函数就会非常的庞大。

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

解决方案3: 使用Promise

我猜你应该知道什么是Promise.如果你不知道, 请先阅读这一篇

Promise能让回调地狱更易于管理.而不是像上面的嵌套回调.你将看到像这样:

const makeBurger = () => {  return getBeef()    .then(beef => cookBeef(beef))    .then(cookedBeef => getBuns(beef))    .then(bunsAndBeef => putBeefBetweenBuns(bunsAndBeef));};// Make and serve burgermakeBurger().then(burger => serve(burger));复制代码

如果你更倾向于单参数风格的promise, 你能把上面的例子转换成这样

const makeBurger = () => {  return getBeef()    .then(cookBeef)    .then(getBuns)    .then(putBeefBetweenBuns);};// Make and serve burgermakeBurger().then(serve);复制代码

更易读和管理。

但是问题是, 如何把回调地狱转换成Promise?

把回调转成Promise

为了转成Promise, 我们需要为每个回调先new一个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);  });};复制代码

在我们的练习中,可能已经为您编写了回调函数。如果使用Node,则包含回调的每个函数将具有相同的语法:

  1. 回调函数将是最后一个参数
  2. 这个回调函数将一直有两个参数。这些参数都有相同的顺序(error是第一个, 随后是你感兴趣的任何东西)
// The function that’s defined for youconst functionName = (arg1, arg2, callback) => {  // Do stuff here  callback(err, stuff);};// How you use the functionfunctionName(arg1, arg2, (err, stuff) => {  if (err) {  console.error(err);  }  // Do stuff});复制代码

如果你的回调已经有了相同的语法, 你可以使用像ES6 Promisify or Denodeify 把回调转换成Promise.如果你使用Node v8.0或者之上, 你可以使用util.promisify.

他们三个都可以使用。你可以选择任意一个库来使用。不过,每种方法之间都有细微的差别。我建议你查看它的文档,了解方法。

解决方案4: 使用异步函数(async/await)

为了使用异步函数, 你首先需要知道两件事。

  1. 如何把callback转换为Promise(就是我们解决方案三中说的内容)
  2. 怎么使用异步函数(如果你需要帮助,你可以读一下这篇)

使用异步函数,您可以编写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 burgermakeBurger().then(serve);复制代码

我们可以在这里对makeBurger进行一项改进。 你可以同时获得两个getBuns和getBeef的助手。 这意味着您可以使用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 burgermakeBurger().then(serve);复制代码

(注意: 你可以在Promise做到相同的效果, 但是它的语法并不是很棒, 不像async/await那么清晰。)

总结

回调地狱并不是像你想象中的那么可怕.这里有四种方式管理回调地狱。

  1. 写注释
  2. 切分函数为更小的函数
  3. 使用Promise
  4. 使用async/await

转载地址:http://rnqwa.baihongyu.com/

你可能感兴趣的文章
一点IT"边缘化"的人的思考
查看>>
WPF 降低.net framework到4.0
查看>>
搭建一个通用的脚手架
查看>>
开年巨制!千人千面回放技术让你“看到”Flutter用户侧问题
查看>>
开源磁盘加密软件VeraCrypt教程
查看>>
本地vs云:大数据厮杀的最终幸存者会是谁?
查看>>
阿里云公共镜像、自定义镜像、共享镜像和镜像市场的区别 ...
查看>>
shadowtunnel v1.7 发布:新增上级负载均衡支持独立密码
查看>>
Java线程:什么是线程
查看>>
mysql5.7 创建一个超级管理员
查看>>
【框架整合】Maven-SpringMVC3.X+Spring3.X+MyBatis3-日志、JSON解析、表关联查询等均已配置好...
查看>>
要想成为高级Java程序员需要具备哪些知识呢?
查看>>
带着问题去学习--Nginx配置解析(一)
查看>>
onix-文件系统
查看>>
java.io.Serializable浅析
查看>>
我的友情链接
查看>>
多线程之线程池任务管理通用模板
查看>>
CSS3让长单词与URL地址自动换行——word-wrap属性
查看>>
CodeForces 580B Kefa and Company
查看>>
开发规范浅谈
查看>>