函数装饰器:将回调转换为promises,还支持逆向转换

252 阅读5分钟

译者:echodis

原文链接

geralt @ pixelbay

我每天都在JavaScript领域工作,我困在了回调,promises或async/await等各种异步调用方案之中。关于如何处理异步代码,我有自己的偏好,虽然有时候我别无选择,因为外部依赖库像fsaws-sdk等在使用别的解决方案。

在所有的代码库中我不喜欢的一件事就是不一致。 所以,如果我开始使用promises,我将继续使用promises。

这就引发了一个小问题:如何在依赖了外部库的项目中保持一致性。那么,正如这篇文章的标题所展示的,我将转换它们。

Callbacks和Promises

我遇到的最常见的情况是比较旧的库使节点式回调,而我希望使用promises或async/awaits等类似的东西。

回顾一下,你的典型的节点式回调可能如下所示:

fs.readFile('./kittens.txt', (err, data) => {
  if (err) throw err;
  console.log(data);
});

但下面才是我想要使用的方法:

fs.readFile('./kittens.txt')
  .then(data => console.log(data))

很多时候,我只是放弃然后使用bluebird.jspromisify。这是最简单,最快和_懒_的方法。 但……

引入一个库但是只使用单个函数是懒惰并且不被提倡的。

……尤其是当需要依赖的函数实现起来很简单的时候。(谷歌搜索 leftpad broke the internet)

现在就停下来,看看你的node_modules文件夹的大小。太讨厌了!node_modules文件夹超过1GB甚至2GB的情况并不罕见!

这些依赖加起来就那么多。

Promisify

我们想要的是一个函数装饰器

一个** 函数装饰器 **是一个高阶函数,它将一个函数作为参数,返回另一个函数,返回的函数是参数函数的变体。 来源

“promisify”函数装饰器的使用示例如下:

const readFile = promisify(fs.readFile)

readFile('./kittens.txt')
  .then(data => console.log(data))

readFile函数仍然与fs.readFile强相关, 但是已经被转换成了一个不同的函数。这就是一个函数装饰器

这应该很容易做到。首先我们创建一个promisify函数,它需要一个参数func并返回一个函数。当返回的函数被调用时,它将返回一个promise。

function promisify(func) {
  return () =>
    new Promise((resolve, reject) => { })
}

我们使用上面的关键字function是因为我们需要访问func中的this,但箭头函数是无法访问的。

注意(下面)如何在args的末尾附加一个附加参数callback。 这是因为我们的装饰函数不会将回调作为参数,而原来的函数func会这么做。

func.apply(this, [...args, callback])

放在一起开始看起来像这样:

function promisify(func) {
  return (**...args**) =>
    new Promise((resolve, reject) => {
      **const callback = ???**

 **func.apply(this, [...args, callback]);**
    })
}

最后一步也是最简单的,即创建“回调”。callback只是一个节点式的callback,它会根据err的存在与否调用resolvereject

Callbackify

我遇到了一个不太常见的情况,我需要的是相反的异步调用方式。 我必须创建的函数需要一个节点式的回调函数。额,我的代码已经用promise的形式写完了。

该代码是针对AWS lambdas的,通常以回调函数方式编写,所以我的代码最终看起来像这样:

module.exports.handler **=** function(event, context, **callback**) {
  myHandler(event)
    .then(data => **callback(null, data)**)
    .catch(err => **callback(err)**)
}

因为我的依赖库都是基于Promise的,所以我更喜欢写这样的东西:

module.exports.handler = (event, context) =>
  myService(event) // note: I don’t need context.

我最终创建了promisifycallbackify的逆向处理,所以我可以像这样创建我的lambda:

module.exports.handler = callbackify((event, context) =>
  myService(event))

由于我们的做法和上述大致相同,所以我们可以通过这一点更快地进行压缩。让我们用一个参数func创建callbackify函数,这个函数返回一个函数。 由于返回的函数将使用args + callback作为参数,我们将不得不将它们分开。

function callbackify(func) {
  return function(...args) {
 **const onlyArgs = args.slice(0, args.length - 1)
    const callback = args[args.length - 1]**
  }
}

最后,我们需要在func上调用apply,并传入我们的onlyArgs参数。

async / await如何适用以上方案呢?

async和await的运行几乎完全和promises一样,所以你可以互换使用它们。

const readFile = promisify(fs.readFile)
const file = await readFile('./kittens.txt)

console.log(file)

检查一下,你可以await一个节点式的callback函数了。整齐一致!

重点

无论你是使用回调,promises还是async/await,最重要的是要保持一致。

在引入依赖库之前尝试编写自己的函数。

如果要调整库中函数的工作原理,请创建一个函数装饰器

Github

我将这些函数放在github上,以便我可以轻松地将它们导入到我的项目,因为我几乎在每个项目中都使用它们。欢迎你使用它们。

[joelnet/functional-helpers functional-helpers — Functional helpers_github.com](github.com/joelnet/fun… ""github.com/joelnet/fun…](github.com/joelnet/fun…)

相关内容

util.promisify被加入到node

bluebird的promisify

结语

如果你觉得这篇文章有趣,或者对函数式编程感兴趣,你可能会喜欢我的其他文章。

[Joel Thoms撰写的最新故事 - Medium _阅读Joel Thoms在Medium上撰写的最新故事。21年来计算机科学与技术传道者_访问medium.com](medium.com/@joelthoms/… ""medium.com/@joelthoms/…](medium.com/@joelthoms/…)

我知道这是一件小事,但是当我在Medium和Twitter(@joelnet上获得这些关注通知时,这让我很激动。或者如果你觉得我太过自满,请在下面的评论中告诉我。

感谢!

黑客的下午 是关于黑客们如何开始下午的。 我们是@AMI家族的一部分。我们正在接受提交并非常乐意接受广告和赞助机会。

如果你喜欢这个故事, 我们强烈建议阅读我们最新的科技文章以及热门科技故事。不到未来,不要把现实世界当作理所当然!