Skip to content

Commit

Permalink
switch from the traditional callback API to Observable
Browse files Browse the repository at this point in the history
  • Loading branch information
shinnn committed Dec 15, 2017
1 parent 4e91939 commit 686ac14
Show file tree
Hide file tree
Showing 6 changed files with 3,280 additions and 3,328 deletions.
7 changes: 5 additions & 2 deletions .editorconfig
Expand Up @@ -3,10 +3,13 @@ root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
indent_style = tab
tab_width = 2
insert_final_newline = true
trim_trailing_whitespace = true

[*.{md,yml}]
indent_style = space

[*.md]
trim_trailing_whitespace = false
75 changes: 39 additions & 36 deletions README.md
Expand Up @@ -5,17 +5,25 @@
[![Build status](https://ci.appveyor.com/api/projects/status/ia3h5bcsy84vgfpc?svg=true)](https://ci.appveyor.com/project/ShinnosukeWatanabe/read-multiple-files)
[![Coverage Status](https://img.shields.io/coveralls/shinnn/read-multiple-files.svg)](https://coveralls.io/r/shinnn/read-multiple-files)

A [Node.js](https://nodejs.org/) module to read multiple files asynchronously
Read multiple files [Observable](https://github.com/tc39/proposal-observable) way

```javascript
const readMultipleFiles = require('read-multiple-files');

readMultipleFiles(new Set(['one.txt', 'another.txt']), (err, bufs) => {
if (err) {
throw err;
readMultipleFiles(new Set([
'one.txt', // 'a'
'another.txt' // 'b'
])).subscribe({
next(result) {
if (result.path === 'one.txt') {
result.contents; // Buffer.from('a')
} else if (result.path === 'another.txt') {
result.contents; // Buffer.from('b')
}
},
complete() {
console.log('Successfully read all files.');
}

bufs; //=> [<Buffer ... >, <Buffer ... >]
});
```

Expand All @@ -33,49 +41,44 @@ npm install read-multiple-files
const readMultipleFiles = require('read-multiple-files');
```

### readMultipleFiles(*paths* [, *options*], *callback*)
### readMultipleFiles(*paths* [, *options*])

*paths*: `<Array|Set<string|Buffer|URL|integer>>` (file paths)
*options*: `Object` ([fs.readFile] options) or `string` (encoding)
*callback*: `Function`

#### callback(*error*, *contents*)

*error*: `Error` if it fails to read at least one of the files, otherwise `null`
*contents*: `Array<Buffer>` or `Array<string>` (according to `encoding` option)
*options*: `Object` ([`fs.readFile`](https://nodejs.org/api/fs.html#fs_fs_readfile_path_options_callback) options) or `string` (encoding)
Return: [`Observable`](https://tc39.github.io/proposal-observable/#observable) ([zenparsing's implementation](https://github.com/zenparsing/zen-observable))

The second argument will be an array of file contents. The order of contents follows the order of file paths.

It automatically strips [UTF-8 byte order mark](https://en.wikipedia.org/wiki/Byte_order_mark#UTF-8) from results.
When the `Observable` is [subscribe](https://tc39.github.io/proposal-observable/#observable-prototype-subscribe)d, it starts to read files in parallel, successively send each result to its [`Observer`](https://github.com/tc39/proposal-observable#observer) as an `Object`: `{path: <string|Buffer|URL|integer>, contents: <string:Buffer>}`

```javascript
const readMultipleFiles = require('read-multiple-files');

// foo.txt: Hello
// bar.txt: World

readMultipleFiles(['foo.txt', 'bar.txt'], 'utf8', (err, contents) => {
if (err) {
throw err;
readMultipleFiles([
'foo.txt', // 'Hello'
'bar.txt' // 'World'
], 'utf8').subscribe({
next({path, contents}) {
if (path === 'one.txt') {
contents; // 'Hello'
} else if (path === 'another.txt') {
contents; // 'World'
}
}

contents; //=> ['Hello', 'World']
});
```

If it fails to read at least one of the files, it passes an error to the first argument and doesn't pass any values to the second argument.
The `Observer` receives an error when it fails to read at least one of the files.

```javascript
const readMultipleFiles = require('read-multiple-files');

// foo.txt: exists
// bar.txt: doesn't exist
// baz.txt: exists

readMultipleFiles(['foo.txt', 'bar.txt', 'baz.txt'], (err, contents) => {
err.code; //=> 'ENOENT'
contents; //=> undefined
arguments.length; //=> 1
readMultipleFiles([
'foo.txt', // exists
'bar.txt' // doesn't exist
]).subscribe({
error(err) {
err.code; //=> ENOENT
},
complete() {
// `complete` callback will never be called.
}
});
```

Expand Down
77 changes: 31 additions & 46 deletions index.js
@@ -1,54 +1,39 @@
/*!
* read-multiple-files | MIT (c) Shinnosuke Watanabe
* https://github.com/shinnn/read-multiple-files
*/
'use strict';

const {promisify} = require('util');

const inspectWithKind = require('inspect-with-kind');
const {readFile} = require('graceful-fs');
const runParalell = require('run-parallel');
const stripBom = require('strip-bom');
const stripBomBuf = require('strip-bom-buf');
const Observable = require('zen-observable');

const ARG_ERR = 'Expected 2 or 3 arguments (paths: <Array|Set>[, options: <Object>], callback: <Function>)';
const promisifiedReadFile = promisify(readFile);

module.exports = function readMultipleFiles(...args) {
const argLen = args.length;

if (argLen !== 2 && argLen !== 3) {
throw new RangeError(`${ARG_ERR}, but got ${
argLen === 1 ? '1 argument' : `${argLen || 'no'} arguments`
} instead.`);
}

const [filePaths, options, cb] = argLen === 3 ? args : [args[0], {}, args[1]];

if (typeof cb !== 'function') {
throw new TypeError(inspectWithKind(cb) +
' is not a function. Last argument to read-multiple-files must be a callback function.');
}

if (!Array.isArray(filePaths) && !(filePaths instanceof Set)) {
throw new TypeError(inspectWithKind(filePaths) +
' is neither Array nor Set. First Argument to read-multiple-files must be file paths (<Array> or <Set>).');
}

runParalell([...filePaths].map(filePath => done => readFile(filePath, options, done)), (err, results) => {
if (err) {
cb(err);
return;
}

if (results.length === 0) {
cb(null, results);
return;
}

if (Buffer.isBuffer(results[0])) {
cb(null, results.map(stripBomBuf));
return;
}

cb(null, results.map(stripBom));
});
return new Observable(observer => {
const argLen = args.length;

if (argLen !== 1 && argLen !== 2) {
throw new RangeError(`Expected 1 or 2 arguments (paths: <Array|Set>[, options: <Object>]), but got ${
argLen === 0 ? 'no' : argLen
} arguments instead.`);
}

const [paths, options] = args;

if (!Array.isArray(paths) && !(paths instanceof Set)) {
const error = new TypeError(`Expected file paths (<Array> or <Set>), but got ${
inspectWithKind(paths)
} instead.`);

error.code = 'ERR_INVALID_ARG_TYPE';
throw error;
}

Promise.all([...paths].map(async path => {
observer.next({
path,
contents: await promisifiedReadFile(path, options)
});
})).then(() => observer.complete(), err => observer.error(err));
});
};

0 comments on commit 686ac14

Please sign in to comment.