2017-04-16 43 views
3

我有一系列接受回调的函数,它们应该互相馈送,每个函数都接受一个回调函数,还有一个接受回调函数的“主要”函数。 this.app指的是一个类的成员(es6)。我想从异步模块更换,以异步调用,用ES6的现代工具:调用一组函数,每个函数都会收到一个回调

firstFunction(next){ 
    if(this.app.isValid()) { 
    next(null, app); 
    } else { 
    next(thia.app.sendError(), null) 
    } 
} 

secondFunc(next){ 
    this.app.data = { 
     reader: "someone" 
    }; 
    next(null, this.app); 
} 

thirdFunc(next){ 
    next(null, this.app); 
} 

majorStuff(next){ 
    //USING async module (I don't want that) 
    // async.series([ 
    // firstFunction, 
    //  secondFunction, 
    // thirdFunction 
    // ], (err, result) => { 
    // if(err) { 
    //  next({ 
    //   success: false, 
    //   message: err 
    //  }) 
    // } else { 
    //  next({ 
    //   success: true, 
    //   message: "Welcome to Mars!" 
    //  }) 
    // } 
    // }); 

    <using babel-polyfill, stage-0 + es2015 presets instead> 
} 
+0

你为什么不使用的承诺? –

+0

这些功能都没有做任何异步。你可以迭代它们并用回调调用:'const majorStuff = next => [firstFunction,secondFunc,thirdFunc] .forEach(fn => fn(next))''。如果这不是您要查找的内容,请更新问题,以便更容易理解问题所在。 –

+0

谢谢你们。基本上它是为异步行为做准备(设计级别,在接收第三方api使用异步操作之前)。 这就是为什么我希望使用aysnc.seriers的行为。 – Chen

回答

1

我有一系列的接受回调函数,并应喂对方,每一个反过来

但你在一个愚蠢的方式写你的函数。他们如何饲料如果每个人只接受回调?为了创建从一个功能到另一个功能的通用数据流,每个功能需要以统一的方式编写。让我们先来看看你的功能

// 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个功能。例如(firstsecondthird)这就是意味着链在一起的功能,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。

+1

非常感谢你的回应。我知道如何很好地使用promises,并试图做一些不同的事情,因为我看到了一个使用await.series的例子。我对此有点好奇。 cps的思想非常酷,功能强大,它让我想起了ramda.js的用法。是的 - 坚持承诺与异步/等待的想法可能是最好的。 – Chen

0

如果你的函数是异步的,然后通过一个函数发生器考虑协调:

// Code goes here 
var app = {}; 

function firstFunction(){ 
    if(isValid(app)) { 
    setTimeout(function(){ 
     gen.next(app); 
    }, 500); 
    } else { 
    gen.next(null); 
    } 
    function isValid(app) { 
    return true; 
    } 
} 

function secondFunc(app){ 
    setTimeout(function(){ 
     app.data2 = +new Date(); 
     gen.next(app); 
    }, 10); 
} 

function thirdFunc(app){ 
    setTimeout(function(){ 
     app.data3 = +new Date(); 
     gen.next(app); 
    }, 0); 
} 

function* majorStuff(){ 
    var app = yield firstFunction(); 
    app = yield secondFunc(app); 
    app = yield thirdFunc(app); 
    console.log(app); 
} 

var gen = majorStuff(); 
gen.next(); 
-1

基本上由刚看看这个例子,我没有理由使用任何异步相关的东西。但是,如果你想重现此使用async - await那么这里是一个办法做到这一点:

首先改变你的方法,让他们返回Promise秒。 Promise要么用一个值来解析,要么用一个错误来拒绝。

const firstFunction() { 
    return new Promise((resolve, reject) => { 
    if(this.app.isValid()) { 
     resolve(app) 
    } else { 
     // assuming sendError() returns the error instance 
     reject(thia.app.sendError()) 
    } 
    }) 
} 

secondFunc() { 
    return new Promise(resolve => { 
    this.app.data = { 
     // its not a good idea to mutate state and return a value at the same time 
     reader: "someone" 
    } 
    resolve(this.app) 
    }) 
} 

thirdFunc(){ 
    return new Promise(resolve => resolve(this.app)) 
} 

现在,你有你的承诺返回功能,您可以在等待他们的异步函数:

async majorStuff() { 
    try { 
    await Promise.all(
     this.firstFunction(), 
     this.secondFunc(), 
     this.thirdFunc() 
    ) 
    return { success: true, message: "Welcome to Mars!" } 
    } catch(e) { 
    return { success: false, message: e.message } 
    } 
} 

或将其用作常规的承诺:如果你想要一个

const result = Promise.all(
    this.firstFunction(), 
    this.secondFunc(), 
    this.thirdFunc() 
).then(() => ({ success: true, message: "Welcome to Mars!" })) 
.catch(e => ({ success: false, message: e.message })) 

外部API可以挂钩到你的方法,那么你可以使用这些可组合的部分来做到这一点,无论你想要的。

如果你想确保你的承诺序列中运行,你可以做这样的事情:

const runSeries = (promiseCreators, input) => { 
    if (promiseCreators.length === 0) { 
    return Promise.resolve(input) 
    } 
    const [firstCreator, ...rest] = promiseCreators 
    return firstCreator(input).then(result => runSeries(rest, result)) 
} 

runSeries([ 
    input => Promise.resolve(1 + input), 
    input => Promise.resolve(2 + input), 
    input => Promise.resolve(3 + input), 
], 0).then(console.log.bind(console)) // 0 + 1 => 1 + 2 => 3 + 3 => 6 

功能runSeries需要承诺创造者(即返回一个承诺函数)的阵列和从给定的输入开始运行它们,然后运行前面的承诺。这个数字接近async.series。你显然可以根据自己的需要调整它以更好地处理论点。

+0

这个答案假定'first','second'和'third'可以并行运行,但是问题显示需要一个序列顺序:即*“应该互相送入,每个都轮流运行*” – naomik

+0

@ naomik问题可能会这么说,但是如果你看看OPs代码(我确信你确实在研究你的答案的广度,道具),你可以看到他正在使用['async.series'](https:/ /caolan.github.io/async/docs.html#series),它完全符合'Promise.all' - >'then'组合的作用 - 根据什么是“馈送”到最终回调 - 除了它让所有'承诺'并行运行。 –

+0

为了满足串联运行的需要,还为此添加了解决方案。 –

2

你可以简单地模仿async.series接口:

function series(fns, cb) { 
    const results = []; 

    const s = fns.map((fn, index) =>() => { 
    fn((err, result) => { 
     if (err) return cb(err, null); 
     results.push(result); 
     if (s[index + 1]) return setImmediate(s[index + 1]); 
     return cb(null, results); 
    }); 
    }); 

    s[0](); 
} 

然后调用它像这样:

series([ 
    first, 
    second, 
    third 
], (err, results) => console.log(err, results)); 
+0

不错!非常有趣的解决方案。 – Chen

相关问题