2017-04-16 148 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

相關問題