Отображение на массив задач в 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, потому что без него может потребоваться инъекция, что трудно для начинающего, как я, понять это правильно.