在Javascript中对任务数组进行映射

9

所以我开始研究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个回答

12
'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.sequenceArray (Task String)转换为Task (Array String),并使用R.unnestTask (Task (Array String))转换为Task (Array String)

如果您还没有这样做,我建议您查看plaid/async-problem


1
谢谢。这实际上非常有趣。这是一种思维方式的转变,我一直在努力应对。回到 Dr Boolean 的书上。 - SpaceBeers
我在 Ramda 文档中找不到 commute 方法。我错过了什么? - akaphenom
也,由于我找不到文档,如何理解commute与“control.monads”序列函数之间的区别? - akaphenom
R.commuteramda/ramda#1467 中更名为 R.sequence。我会更新我的答案。 - davidchambers

4
正如David所建议的那样,commute对于将一些applicatives(比如Task)的列表转换为包含值列表的单个applicative非常有用。

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

我们从未进入fs.readfile(),但'file'是实际的文件名。

密切相关的commuteMap在这里也很有用,因为它会处理分离的map步骤,这意味着上面的代码也可以表示为:
var app = compose(chain(commuteMap(readFile, Task.of)), readDirectories);

太好了。谢谢。 - SpaceBeers

0

我曾经遇到过类似的问题,需要读取目录中的所有文件,我使用了Ramda的pipeP函数来解决:

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

当使用pipeP读取文件时,似乎需要Promise.all来接收值或承诺的数组,但是我不明白为什么必须使函数返回Promise.all而不是内联它。

您对任务/分支的使用很有趣,因为错误处理已经内置。希望pipeP有一个catch块,因为没有它需要注入maybe,这对像我这样的初学者来说很难做到。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接