@stdlib/fs
Version:
Filesystem APIs.
203 lines (181 loc) • 5.19 kB
JavaScript
/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
;
// MODULES //
var logger = require( 'debug' );
var isStringArray = require( '@stdlib/assert/is-string-array' ).primitives;
var isString = require( '@stdlib/assert/is-string' ).isPrimitive;
var isFunction = require( '@stdlib/assert/is-function' );
var assign = require( '@stdlib/object/assign' );
var readFile = require( './../../read-file' );
var format = require( '@stdlib/string/format' );
var config = require( './config.json' );
var delay = require( './delay.js' );
var clearPending = require( './clear_pending.js' );
// VARIABLES //
var debug = logger( 'read-file-list:async' );
var MAX_RETRIES = config.max_retries;
var MAX_DELAY = config.max_delay;
// MAIN //
/**
* Asynchronously reads the entire contents of each file in a file list.
*
* @param {StringArray} list - list of file paths
* @param {(Object|string)} [options] - options
* @param {(string|null)} [options.encoding] - file encoding
* @param {string} [options.flag] - file status flag
* @param {Function} clbk - callback to invoke after reading file contents
* @throws {TypeError} first argument must be an array of strings
* @throws {TypeError} callback argument must be a function
*
* @example
* var list = [ __filename ];
*
* readFileList( list, onFiles );
*
* function onFiles( error, files ) {
* if ( error ) {
* throw error;
* }
* console.dir( files );
* }
*/
function readFileList( list, options, clbk ) {
var pending;
var results;
var errFLG;
var count;
var opts;
var len;
var cb;
var i;
if ( !isStringArray( list ) ) {
throw new TypeError( format( 'invalid argument. First argument must be an array of strings. Value: `%s`.', list ) );
}
if ( arguments.length < 3 ) {
opts = {};
cb = options;
} else {
if ( isString( options ) ) {
opts = options;
} else {
opts = assign( {}, options );
}
cb = clbk;
}
if ( !isFunction( cb ) ) {
throw new TypeError( format( 'invalid argument. Callback argument must be a function. Value: `%s`.', cb ) );
}
len = list.length;
results = new Array( len );
pending = {};
count = 0;
debug( 'Reading %d files...', len );
for ( i = 0; i < len; i++ ) {
debug( 'Reading file: %s (%d of %d).', list[ i ], i+1, len );
readFile( list[ i ], opts, getCallback( i ) );
}
/**
* Returns a callback to be invoked upon reading a file.
*
* @private
* @param {NonNegativeInteger} idx - index
* @returns {Callback} callback
*/
function getCallback( idx ) {
var retries;
var file;
var k;
file = list[ idx ];
k = idx + 1;
retries = 0;
/**
* Retries reading a file.
*
* @private
*/
function retry() {
delete pending[ idx ];
debug( 'Reading file: %s (%d of %d). Retry: %d of %d.', file, k, len, retries, MAX_RETRIES );
readFile( file, opts, onRead );
}
/**
* Callback to be invoked upon reading a file.
*
* @private
* @param {(Error|null)} error - error object
* @param {(Buffer|string)} data - file data
* @returns {void}
*/
function onRead( error, data ) {
var d;
if ( errFLG ) {
debug( 'An error has already been returned. Discarding data for file: %s (%d of %d).', file, k, len );
return; // prevents `done()` from being called more than once
}
if ( error ) {
debug( 'Encountered an error when reading %s (%d of %d). Error: %s', file, k, len, error.message );
if (
error.code === 'EMFILE' || // current process
error.code === 'ENFILE' // across entire system
) {
retries += 1;
if ( retries > MAX_RETRIES ) {
debug( 'Maximum number of retries exceeded. Too many open files.' );
error = new Error( 'unexpected error. Max retries exceeded. Too many open files.' );
return done( error );
}
d = delay( retries, MAX_DELAY );
debug( 'Too many open files. Will retry reading file %d of %d in %s seconds.', k, len, d/1000 );
pending[ idx ] = setTimeout( retry, d );
return;
}
return done( error );
}
debug( 'Successfully read file: %s (%d of %d).', file, k, len );
results[ idx ] = {
'file': file,
'data': data
};
count += 1;
debug( 'Read %d of %d files.', count, len );
if ( count === len ) {
return done();
}
}
return onRead;
}
/**
* Callback invoked upon completion.
*
* @private
* @param {Error} [error] - error object
* @returns {void}
*/
function done( error ) {
clearPending( pending );
if ( error ) {
errFLG = true;
return cb( error );
}
debug( 'Successfully read all files.' );
cb( null, results );
}
}
// EXPORTS //
module.exports = readFileList;