一个任务有几个步骤,如果每个步骤的输入仅仅是直接的最后一步,那就很容易了。然而,更多的情况是,某些步骤不仅取决于直接的最后一步。
我可以通过几种方法来完成工作,但最后都是丑陋的嵌套代码,我希望任何人都能帮助我找到更好的方法。
为了演示,我创建了以下类似于签名的示例,流程有3个步骤,如下所示:
#第3步不仅依赖于step#2,还依赖于step#1.
下面是使用folktale2进行的jest单元测试
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-我认为另一种方法是将所有输出置于将通过的状态,但它可能非常类似于attempt#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)
})发布于 2017-09-06 22:21:50
您的示例可以编写如下:
const withConnection = connection =>
findOneAccount(connection)
.map(x => x.id)
.chain(createToken(connection))
getDbConnection().chain(withConnection)这类似于您的第二次尝试,但是使用chain而不是ap/lift来消除对后续chain(identity)的需求。如果您愿意的话,这也可以更新为使用converge,尽管我觉得它在这个过程中失去了大量的可读性。
const withConnection = R.converge(R.chain, [
createToken,
R.compose(R.map(R.prop('id')), findOneAccount)
])
getDbConnection().chain(withConnection)还可以对其进行更新,使其看起来类似于使用生成器的第三次尝试。下面的Do函数定义可以被提供某种形式的"do语法“的现有库替换。
// sequentially calls each iteration of the generator with `chain`
const Do = genFunc => {
const generator = genFunc()
const cont = arg => {
const {done, value} = generator.next(arg)
return done ? value : value.chain(cont)
}
return cont()
}
Do(function*() {
const connection = yield getDbConnection()
const account = yield findOneAccount(connection)
return createToken(connection)(account.id)
})https://stackoverflow.com/questions/46070249
复制相似问题