3

一个任务有几个步骤,如果每个步骤的输入只是直接来自最后一步,那很容易。然而,更多的时候,一些步骤不仅仅依赖于直接的最后一步。

我可以通过多种方式解决,但最终都会得到丑陋的嵌套代码,我希望任何人都可以帮助我找到更好的方法。

我创建了以下类似登录的示例来演示,该过程分为以下 3 个步骤:

  1. 获取数据库连接 (() -> 任务连接)
  2. 查找帐户(连接 -> 任务帐户)
  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)
})
4

1 回答 1

3

你的例子可以写成如下:

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)
})
于 2017-09-06T22:21:50.127 回答