一个任务有几个步骤,如果每个步骤的输入只是直接来自最后一步,那很容易。然而,更多的时候,一些步骤不仅仅依赖于直接的最后一步。
我可以通过多种方式解决,但最终都会得到丑陋的嵌套代码,我希望任何人都可以帮助我找到更好的方法。
我创建了以下类似登录的示例来演示,该过程分为以下 3 个步骤:
- 获取数据库连接 (() -> 任务连接)
- 查找帐户(连接 -> 任务帐户)
- 创建令牌(连接 -> accountId -> 任务令牌)
#step3 不仅取决于步骤#2,还取决于步骤#1。
下面是使用folktale2的笑话单元测试
import {task, of} from 'folktale/concurrency/task'
import {converge} from 'ramda'
const getDbConnection = () =>
task(({resolve}) => resolve({id: `connection${Math.floor(Math.random()* 100)}`})
)
const findOneAccount = connection =>
task(({resolve}) => resolve({name:"ron", id: `account-${connection.id}`}))
const createToken = connection => accountId =>
task(({resolve}) => resolve({accountId, id: `token-${connection.id}-${accountId}`}))
const liftA2 = f => (x, y) => x.map(f).ap(y)
test('attempt#1 pass the output one by one till the step needs: too many passing around', async () => {
const result = await getDbConnection()
.chain(conn => findOneAccount(conn).map(account => [conn, account.id])) // pass the connection to next step
.chain(([conn, userId]) => createToken(conn)(userId))
.map(x=>x.id)
.run()
.promise()
console.log(result) // token-connection90-account-connection90
})
test('attempt#2 use ramda converge and liftA2: nested ugly', async () => {
const result = await getDbConnection()
.chain(converge(
liftA2(createToken),
[
of,
conn => findOneAccount(conn).map(x=>x.id)
]
))
.chain(x=>x)
.map(x=>x.id)
.run()
.promise()
console.log(result) // token-connection59-account-connection59
})
test('attempt#3 extract shared steps: wrong', async () => {
const connection = getDbConnection()
const accountId = connection
.chain(conn => findOneAccount(conn))
.map(result => result.id)
const result = await of(createToken)
.ap(connection)
.ap(accountId)
.chain(x=>x)
.map(x=>x.id)
.run()
.promise()
console.log(result) // token-connection53-account-connection34, wrong: get connection twice
})
尝试#1 是对的,但我必须将非常早的步骤的输出传递到需要它的步骤,如果它跨越许多步骤,那很烦人。
尝试#2 也是正确的,但以嵌套代码告终。
我喜欢尝试#3,它使用一些变量来保存值,但不幸的是,它不起作用。
Update-1 我认为另一种方法可以将所有输出置于将通过的状态,但它可能非常相似的尝试#1
test.only('attempt#4 put all outputs into a state which will pass through', async () => {
const result = await getDbConnection()
.map(x=>({connection: x}))
.map(({connection}) => ({
connection,
account: findOneAccount(connection)
}))
.chain(({account, connection})=>
account.map(x=>x.id)
.chain(createToken(connection))
)
.map(x=>x.id)
.run()
.promise()
console.log(result) // token-connection75-account-connection75
})
update-2
通过使用@Scott 的do
方法,我对以下方法非常满意。它又短又干净。
test.only('attempt#5 use do co', async () => {
const mdo = require('fantasy-do')
const app = mdo(function * () {
const connection = yield getDbConnection()
const account = yield findOneAccount(connection)
return createToken(connection)(account.id).map(x=>x.id)
})
const result = await app.run().promise()
console.log(result)
})