How to deal with nested callbacks and avoid “callback hell”(翻译)

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

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 burger
makeBurger(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 step
const 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 burger
makeBurger().then(burger => serve(burger));
复制代码

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

const makeBurger = () => {
  return getBeef()
    .then(cookBeef)
    .then(getBuns)
    .then(putBeefBetweenBuns);
};
// Make and serve burger
makeBurger().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 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
});
复制代码

如果你的回调已经有了相同的语法, 你可以使用像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 burger
makeBurger().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 burger
makeBurger().then(serve);
复制代码

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

总结

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

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

猜你喜欢

转载自blog.csdn.net/weixin_33801856/article/details/91377377
今日推荐