Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mapping over an array of Tasks in Javascript

So I've started looking at Ramda / Folktale. I'm having an issue trying to map over an array of Tasks that comes from a directory. I'm trying to parse file contents.

var fs = require('fs');
var util = require('util');
var R = require('ramda');
var Task = require('data.task');

var compose = R.compose;
var map = R.map;
var chain = R.chain;


function parseFile(data) {
      console.log("Name: " + data.match(/\$name:(.*)/)[1]);
      console.log("Description: " + data.match(/\$description:(.*)/)[1]);
      console.log("Example path: " + data.match(/\$example:(.*)/)[1]);
}

// String => Task [String]
function readDirectories(path) {
    return new Task(function(reject, resolve) {
        fs.readdir(path, function(err, files) {
            err ? reject(err) : resolve(files);
        })
    })
}

// String => Task String
function readFile(file) {
    return new Task(function(reject, resolve) {
        fs.readFile('./src/less/' + file, 'utf8', function(err, data) {
            err ? reject(err) : resolve(data);
        })
    })
}

var app = compose(chain(readFile), readDirectories);

app('./src/less').fork(
    function(error) { throw error },
    function(data)  { util.log(data) }
);

I'm reading the files in a directory and returning a Task. When this resolves it should go into the readFile function (which returns a new task). Once it reads the file I want it to just parse some bits out of there.

With the following:

var app = compose(chain(readFile), readDirectories);

It gets into the readFile function but 'file' is an array of files so it errors.

With:

var app = compose(chain(map(readFile)), readDirectories);

We never get into fs.readfile(), but 'file' is the actual file name.

I'm pretty stumped on this and the documentation is baffling. Any suggestions welcome.

Thanks

like image 787
SpaceBeers Avatar asked Nov 04 '15 09:11

SpaceBeers


People also ask

How do I map an array in JavaScript?

The syntax for the map() method is as follows: arr. map(function(element, index, array){ }, this); The callback function() is called on each array element, and the map() method always passes the current element , the index of the current element, and the whole array object to it.

How does JavaScript array map work?

Definition and Usagemap() creates a new array from calling a function for every array element. map() calls a function once for each element in an array. map() does not execute the function for empty elements. map() does not change the original array.

Can you have an array of maps?

C++ allows us a facility to create an array of maps. An array of maps is an array in which each element is a map on its own.


1 Answers

'use strict';

const fs = require('fs');

const Task = require('data.task');
const R = require('ramda');


//    parseFile :: String -> { name :: String
//                           , description :: String
//                           , example :: String }
const parseFile = data => ({
  name:         R.nth(1, R.match(/[$]name:(.*)/, data)),
  description:  R.nth(1, R.match(/[$]description:(.*)/, data)),
  example:      R.nth(1, R.match(/[$]example:(.*)/, data)),
});

//    readDirectories :: String -> Task (Array String)
const readDirectories = path =>
  new Task((reject, resolve) => {
    fs.readdir(path, (err, filenames) => {
      err == null ? resolve(filenames) : reject(err);
    })
  });

//    readFile :: String -> Task String
const readFile = filename =>
  new Task(function(reject, resolve) {
    fs.readFile('./src/less/' + filename, 'utf8', (err, data) => {
      err == null ? resolve(data) : reject(err);
    })
  });

//    dirs :: Task (Array String)
const dirs = readDirectories('./src/less');

//    files :: Task (Array (Task String))
const files = R.map(R.map(readFile), dirs);

//    sequenced :: Task (Task (Array String))
const sequenced = R.map(R.sequence(Task.of), files);

//    unnested :: Task (Array String)
const unnested = R.unnest(sequenced);

//    parsed :: Task (Array { name :: String
//                          , description :: String
//                          , example :: String })
const parsed = R.map(R.map(parseFile), unnested);

parsed.fork(err => {
              process.stderr.write(err.message);
              process.exit(1);
            },
            data => {
              process.stdout.write(R.toString(data));
              process.exit(0);
            });

I wrote each of the transformations on a separate line so I could include type signatures which make the nested maps easier to understand. These could of course be combined into a pipeline via R.pipe.

The most interesting steps are using R.sequence to transform Array (Task String) into Task (Array String), and using R.unnest to transform Task (Task (Array String)) into Task (Array String).

I suggest having a look at plaid/async-problem if you have not already done so.

like image 118
davidchambers Avatar answered Sep 24 '22 02:09

davidchambers