我有一系列的接受回調函數,並應喂對方,每一個反過來
但你在一個愚蠢的方式寫你的函數。他們如何飼料如果每個人只接受回調?爲了創建從一個功能到另一個功能的通用數據流,每個功能需要以統一的方式編寫。讓我們先來看看你的功能
// 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