Отображение на массив задач в Javascript

Итак, я начал смотреть на Ramda / Folktale. У меня возникла проблема при попытке сопоставить массив задач, поступающих из каталога. Я пытаюсь разобрать содержимое файла.

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) }
);

Я читаю файлы в каталоге и возвращаю задание. Когда это разрешится, оно должно перейти в функцию readFile (которая возвращает новую задачу). Как только он читает файл, я хочу, чтобы он просто проанализировал некоторые биты.

Со следующим:

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

Он попадает в функцию readFile, но "file" - это массив файлов, поэтому он ошибается.

С:

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

Мы никогда не попадаем в fs.readfile(), но "file" - это фактическое имя файла.

Я довольно озадачен этим, и документация сбивает с толку. Любые предложения приветствуются.

Спасибо

3 ответа

Решение
'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);
            });

Я записал каждое из преобразований в отдельной строке, чтобы можно было включать сигнатуры типов, которые облегчают понимание вложенных карт. Они, конечно, могут быть объединены в трубопровод через R.pipe,

Самые интересные шаги используют R.sequence преобразовывать Array (Task String) в Task (Array String) и используя R.unnest преобразовывать Task (Task (Array String)) в Task (Array String),

Я предлагаю взглянуть на проблему пледа / асинхронности, если вы еще этого не сделали.

Как предположил Дэвид, commute полезно для преобразования списка некоторых аппликативов (таких как Task) в единый аппликатив, содержащий список значений.

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

Мы никогда не попадаем в fs.readfile(), но "file" - это фактическое имя файла.

Тесно связанные commuteMap здесь тоже может помочь, так как позаботится об map шаг, означающий, что приведенный выше код также должен быть представлен в виде:

var app = compose(chain(commuteMap(readFile, Task.of)), readDirectories);

У меня была похожая проблема чтения всех файлов в каталоге и я начал с pipeP от ramda:

'use strict';

const fs = require('fs');
const promisify = require("es6-promisify");
const _ = require('ramda');

const path = './src/less/';
const log  = function(x){ console.dir(x); return x };
const map  = _.map;
const take = _.take;

const readdir = promisify(fs.readdir);
const readFile = _.curry(fs.readFile);
const readTextFile = readFile(_.__, 'utf8');
const readTextFileP = promisify(readTextFile);

var app = _.pipeP(
  readdir,
  take(2),  // for debugging, so don’t waste time reading all the files
  map(_.concat(path)),
  map(readTextFileP),
  promiseAll,
  map(take(50)),
  log
  );

function promiseAll(arr) {
  return Promise.all(arr)
}

app(path);

Promise.all, по-видимому, необходим при чтении файлов, поскольку pipeP ожидает значение или обещание, но получает массив обещаний для чтения файлов. Что меня удивляет, так это то, почему я должен был сделать функцию, возвращающую Promise.all вместо того, чтобы включить его.

Ваше использование task / fork интригует, потому что встроена обработка ошибок. Хотелось бы, чтобы pipeP имел блок catch, потому что без него может потребоваться инъекция, что трудно для начинающего, как я, понять это правильно.

Другие вопросы по тегам