我有一系列的接受回调函数,并应喂对方,每一个反过来
但你在一个愚蠢的方式写你的函数。他们如何饲料如果每个人只接受回调?为了创建从一个功能到另一个功能的通用数据流,每个功能需要以统一的方式编写。让我们先来看看你的功能
// only accepts a callback
firstFunction(next){
// depends on context using this
if(this.app.isValid()) {
// calls the callback with error and successful value
next(null, app);
} else {
// calls the callback with error and successful value
next(this.app.sendError(), null)
}
}
我们想使这个通用的,使我们可以在链中组装许多功能。也许我们可以想出一些接口,看起来像这样
// where `first`, `second`, and `third` are your uniform functions
const process = cpscomp (first, second, third)
process(app, (err, app) => {
if (err)
console.error(err.message)
else
console.log('app state', app)
})
这个答案存在,如果有的话,向你展示它是多少工作,延续传递风格来写 - 使用,也许更重要的是,有多少工作承诺节省您。这并不是说CPS没有用例,只是它可能不应该成为异步控制流程的开始。
婴儿学步
我喜欢得到的东西用很少的代码量的工作,所以我所看到的一切是如何结合在一起的。下面我们有3个功能。例如(first
,second
,third
)这就是意味着链在一起的功能,compcps
(代表撰写延续传递风格)
const first = (x, k) => {
k(x + 1)
}
const second = (x, k) => {
k(x * 2)
}
const third = (x, k) => {
k(x * x * x)
}
const compcps = (f, ...fs) => (x, k) => {
if (f === undefined)
k(x)
else
f(x, y => compcps (...fs) (y, k))
}
const process = compcps (first, second, third)
process(1, x => console.log('result', x))
// first(1, x => second(x, y => third(y, z => console.log('result', z))))
// second(2, y => third(y, z => console.log('result', z)))
// third(4, z => console.log('result', z))
// console.log('result', 64)
// result 64
节点式延续传球
节点通过首先将Error(如果存在)传递给回调来添加一层约定。为了支持这一点,我们只需要稍作修改我们的compcps
功能 -
const compcps = (f,...fs) => (x, k) => {
if (f === undefined)
k(null, x)
else
f(x, (err, y) =>err ? k(err, null) : compcps (...fs) (y, k))
}
const badegg = (x, k) => {
k(Error('you got a real bad egg'), null)
}
const process = compcps (first, badegg, second, third)
process(1, (err, x) => {
if (err)
console.error('ERROR', err.message)
else
console.log('result', x)
})
// ERROR you got a real bad egg
的错误直接通过我们的process
回调传递(在大胆的变化),但我们必须要小心!如果有一个疏忽的函数会抛出一个错误,但不会将它传递给回调的第一个参数呢?
const rottenapple = (app, k) => {
// k wasn't called with the error!
throw Error('seriously bad apple')
}
让我们的最终更新我们compcps
功能,将适当漏斗这些错误进入回调,使我们能够正确地处理它们 - (在大胆变化)
const compcps = (f,...fs) => (x, k) => {
try {
if (f === undefined)
k(null, x)
else
f(x, (err, y) => err ? k(err, null) : compcps (...fs) (y, k))
} catch (err) { k(err, null) }
}
const process = compcps (first, rottenapple, second, third)
process(1, (err, x) => {
if (err)
console.error('ERROR', err.message)
else
console.log('result', x)
})
// ERROR seriously bad apple
在您的代码中使用compcps
现在您已经知道您的函数必须如何构建,我们可以轻松编写它们。在下面的代码中,我将通过app
作为从函数到函数的状态,而不是依赖于上下文相关的this
。正如您在main
中看到的那样,可以使用单个compcps
调用很好地表示整个函数序列。
最后,我们运行main
有两个不同的国家看到了不同的结果
const compcps = (f,...fs) => (x, k) => {
try {
if (f === undefined)
k(null, x)
else
f(x, (err, y) => err ? k(err, null) : compcps (...fs) (y, k))
}
catch (err) {
k(err, null)
}
}
const first = (app, k) => {
if (!app.valid)
k(Error('app is not valid'), null)
else
k(null, app)
}
const second = (app, k) => {
k(null, Object.assign({}, app, {data: {reader: 'someone'}}))
}
const third = (app, k) => {
k(null, app)
}
const log = (err, x) => {
if (err)
console.error('ERROR', err.message)
else
console.log('app', x)
}
const main = compcps (first, second, third)
main ({valid: true}, log)
// app { valid: true, data: { reader: 'someone' } }
main ({valid: false}, log)
// ERROR app is not valid
备注
正如其他人评论说,你的代码是唯一做同步的事情。我确定你已经过分简化了你的例子(你不应该这么做),但是我在这个答案中提供的代码可以完全异步运行。每当调用k
时,该序列将移至下一步 - 无论是同步还是异步调用k
。
万事万物都说,延续传球的风格并非没有头痛。有很多小陷阱会遇到。
- 如果回调从未被调用过,该怎么办?我们将如何调试该问题?
- 如果多次调用回调会怎么样?
很多人已经转向使用Promises来处理异步控制流;尤其是因为它们现在已经快速,稳定并得到了Node的本地支持。 API当然是不同的,但它的目的是缓解一些大量使用cps的压力。一旦你学会使用Promise,他们开始感觉很自然。
此外,async/await
是一种新的语法,它极大地简化了使用Promise的所有样板文件 - 最终,异步代码可以非常平坦,非常像它的同步代码。
Promise的方向有巨大的推动力,社区也在背后。如果你被困在写CPS的时候,掌握一些技术是很好的,但是如果你正在编写一个新的应用程序,我会放弃CPS而不是晚些时候支持Promises API。
你为什么不使用的承诺? –
这些功能都没有做任何异步。你可以迭代它们并用回调调用:'const majorStuff = next => [firstFunction,secondFunc,thirdFunc] .forEach(fn => fn(next))''。如果这不是您要查找的内容,请更新问题,以便更容易理解问题所在。 –
谢谢你们。基本上它是为异步行为做准备(设计级别,在接收第三方api使用异步操作之前)。 这就是为什么我希望使用aysnc.seriers的行为。 – Chen