译者:echodis
我每天都在JavaScript领域工作,我困在了回调,promises或async/await等各种异步调用方案之中。关于如何处理异步代码,我有自己的偏好,虽然有时候我别无选择,因为外部依赖库像fs, aws-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.js的promisify。这是最简单,最快和_懒_的方法。 但……
引入一个库但是只使用单个函数是懒惰并且不被提倡的。
……尤其是当需要依赖的函数实现起来很简单的时候。(谷歌搜索 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的存在与否调用resolve或reject。
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.
我最终创建了promisify,callbackify的逆向处理,所以我可以像这样创建我的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…)
相关内容
结语
如果你觉得这篇文章有趣,或者对函数式编程感兴趣,你可能会喜欢我的其他文章。
[Joel Thoms撰写的最新故事 - Medium _阅读Joel Thoms在Medium上撰写的最新故事。21年来计算机科学与技术传道者_访问medium.com](medium.com/@joelthoms/… ""medium.com/@joelthoms/…](medium.com/@joelthoms/…)
我知道这是一件小事,但是当我在Medium和Twitter(@joelnet上获得这些关注通知时,这让我很激动。或者如果你觉得我太过自满,请在下面的评论中告诉我。
感谢!