chaining (or mapping) Task containing a single data array to an array of Tasks

322 views Asked by At

Part of learning Fanatasy Land/Folk Tale has lead me to creating some code. I am essnetially scnaning my network (via someLib) and uploading the results to a mongo repository. The scan returns back an array of results, while the upsert into mongo needs to work on results independently (mongoose - this is my first time with this lib too, so I may be mistaken there). In a traditional promised base model I would

// step 0: setup
const someLibPomise = makePromiseOf(someLib)

//set 1: get data
const dataArray = yield someLibPomise()

//set 2: convert to array of promises to upsert
const promiseArray = _.map(dataArray, makeUpsertPromise)

//step 3: wait on results
const upsertResults = yield promiseArray

In and of itself, this is a pretty clean representation, but I want push my comprehension of these functional techniques. My working version leaves a little bit to be desired, as I cant seem to get from the Task returned by the someLibTask function, which contains an array of objects TO an array of Tasks representing the individual upserts. I feel there must be a better way here is what is working:

// step 0: setup
const someLibTask = Async.liftNode(someLib)

const cleanUpData = (dataArray) => {
   return _.map(dataArray, (data) => { 
         // cleanup data object
         return data 
   })
}

const upsertTask = (collection) => {
    return (criteria, record) => {

        return new Task( (reject, resolve) => {
            const callback =  (error, data) => {
                if (error)  reject(error)
                else        resolve(data)
            }

            collection.findOneAndUpdate(criteria, record, 
                 {upsert: true}, callback)
        })

    }
}

const persist = (data) => {
    mongoose.connect('mongodb://localhost/db');
    const someUpsert = adapt.upsertTask(Some.collection)

    const tasks = _.map(data, (record) => {
        const criteria = { "id": record.id }
        return serverUpsert(criteria, record)
    })

    return Async.parallel(tasks).fork(console.error, process.exit)
}

// step 1: make a query and return an array of objects
// () => Task(object[]) 
const dataTask = someLibTask().map(cleanUpData)

// step 2: for the results to error log or persist method
// (d) => (), (d) => ()
dataTask.fork(console.error, persist)

Ideally i can chain (or map) the results from the dataTask into persist, which converts that individual task to an array of upsert tasks. Which I can then wait on. I would love to see something like:

// step 1: make a query and return an array of objects
const dataTask = someLibTask().map(cleanUpData)

// step 2: chain the results into upsert
const upsertTasks = dataTask.chain(persist)

//step 3: wait on the upsert tasks, and then log results
Async.parallel(upsertTasks).fork(console.error, process.exit)
2

There are 2 answers

0
Bergi On BEST ANSWER

get from a Task which contains an array of objects TO an array of Tasks

That's impossible. You cannot get out of the Task monad. For knowing how many Tasks you would get in that array, you would need to run the initial task first.

What you want seems to be

someLibTask().map(cleanUpData).chain(persist)

which returns another Task for the result of all upserts.

0
akaphenom On

Would welcome advice on how to clean this up further. Perhaps remove the lodash map, in favor of something more idiomatic, also can the final persisAllTask.fork be written better?

The setup:

const someLibTask = Async.liftNode(someLib)

// cleanUpData :: [{}] => [{}]
const cleanUpData = (dataArray) => {
   return _.map(dataArray, (data) => { 
         // cleanup data object
         return data 
   })
}

// upsertTask :: {} => ( {}, {} => Task {} )
const upsertTask = (collection) => {
    return (criteria, record) => {

        return new Task( (reject, resolve) => {
            const callback =  (error, data) => {
                if (error)  reject(error)
                else        resolve(data)
            }

            collection.findOneAndUpdate(criteria, record, 
                 {upsert: true}, callback)
        })

    }
}

 // persist :: [{id :: string, ...}] => [Task {id :: string, ...}]
const persist = (data) => {
    mongoose.connect('mongodb://localhost/db');
    const someUpsert = upsertTask(Some.collection)

    const performUpsert = (serverRecord) => {
        const criteria = { "id": record.id }
        return serverUpsert(criteria, serverRecord)
    }

    return _.map(data, performUpsert)

}

// noop :: _ => unit
const noop = () => {}

// [Task] => unit
const waitOnThenExit = (waitingOn) => {
    Async.parallel(waitingOn).fork(console.error, logThenExit)
}

The process:

// {} => Task [{id :: string, ...}]
const dataTask = someLibTask({}).map(cleanUpData)

// Task [{id :: string, ...}] => Task [Task {id :: string, ...}]
const persistAllTask = dataTask.map(persist)

persistAllTask.fork(
    noop,
    waitOnThenExit)