async.js (4957B)
1 /** 2 * @license Apache-2.0 3 * 4 * Copyright (c) 2018 The Stdlib Authors. 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 'use strict'; 20 21 // MODULES // 22 23 var logger = require( 'debug' ); 24 var isStringArray = require( '@stdlib/assert/is-string-array' ).primitives; 25 var isFunction = require( '@stdlib/assert/is-function' ); 26 var copy = require( '@stdlib/utils/copy' ); 27 var readFile = require( './../../read-file' ); 28 var config = require( './config.json' ); 29 var delay = require( './delay.js' ); 30 var clearPending = require( './clear_pending.js' ); 31 32 33 // VARIABLES // 34 35 var debug = logger( 'read-file-list:async' ); 36 var MAX_RETRIES = config.max_retries; 37 var MAX_DELAY = config.max_delay; 38 39 40 // MAIN // 41 42 /** 43 * Asynchronously reads the entire contents of each file in a file list. 44 * 45 * @param {StringArray} list - list of file paths 46 * @param {(Object|string)} [options] - options 47 * @param {(string|null)} [options.encoding] - file encoding 48 * @param {string} [options.flag] - file status flag 49 * @param {Function} clbk - callback to invoke after reading file contents 50 * @throws {TypeError} first argument must be a string array 51 * @throws {TypeError} callback argument must be a function 52 * 53 * @example 54 * var list = [ __filename ]; 55 * 56 * readFileList( list, onFiles ); 57 * 58 * function onFiles( error, files ) { 59 * if ( error ) { 60 * throw error; 61 * } 62 * console.dir( files ); 63 * } 64 */ 65 function readFileList( list, options, clbk ) { 66 var pending; 67 var results; 68 var errFLG; 69 var count; 70 var opts; 71 var len; 72 var cb; 73 var i; 74 75 if ( !isStringArray( list ) ) { 76 throw new TypeError( 'invalid argument. First argument must be a string array. Value: `' + list + '`.' ); 77 } 78 if ( arguments.length < 3 ) { 79 opts = {}; 80 cb = options; 81 } else { 82 opts = copy( options ); 83 cb = clbk; 84 } 85 if ( !isFunction( cb ) ) { 86 throw new TypeError( 'invalid argument. Callback argument must be a function. Value: `' + cb + '`.' ); 87 } 88 len = list.length; 89 90 results = new Array( len ); 91 pending = {}; 92 count = 0; 93 94 debug( 'Reading %d files...', len ); 95 for ( i = 0; i < len; i++ ) { 96 debug( 'Reading file: %s (%d of %d).', list[ i ], i+1, len ); 97 readFile( list[ i ], opts, getCallback( i ) ); 98 } 99 100 /** 101 * Returns a callback to be invoked upon reading a file. 102 * 103 * @private 104 * @param {NonNegativeInteger} idx - index 105 * @returns {Callback} callback 106 */ 107 function getCallback( idx ) { 108 var retries; 109 var file; 110 var k; 111 112 file = list[ idx ]; 113 k = idx + 1; 114 retries = 0; 115 116 /** 117 * Retries reading a file. 118 * 119 * @private 120 */ 121 function retry() { 122 delete pending[ idx ]; 123 debug( 'Reading file: %s (%d of %d). Retry: %d of %d.', file, k, len, retries, MAX_RETRIES ); 124 readFile( file, opts, onRead ); 125 } 126 127 /** 128 * Callback to be invoked upon reading a file. 129 * 130 * @private 131 * @param {(Error|null)} error - error object 132 * @param {(Buffer|string)} data - file data 133 * @returns {void} 134 */ 135 function onRead( error, data ) { 136 var d; 137 if ( errFLG ) { 138 debug( 'An error has already been returned. Discarding data for file: %s (%d of %d).', file, k, len ); 139 return; // prevents `done()` from being called more than once 140 } 141 if ( error ) { 142 debug( 'Encountered an error when reading %s (%d of %d). Error: %s', file, k, len, error.message ); 143 if ( 144 error.code === 'EMFILE' || // current process 145 error.code === 'ENFILE' // across entire system 146 ) { 147 retries += 1; 148 if ( retries > MAX_RETRIES ) { 149 debug( 'Maximum number of retries exceeded. Too many open files.' ); 150 error = new Error( 'max retries exceeded. Too many open files.' ); 151 return done( error ); 152 } 153 d = delay( retries, MAX_DELAY ); 154 debug( 'Too many open files. Will retry reading file %d of %d in %s seconds.', k, len, d/1000 ); 155 pending[ idx ] = setTimeout( retry, d ); 156 return; 157 } 158 return done( error ); 159 } 160 debug( 'Successfully read file: %s (%d of %d).', file, k, len ); 161 results[ idx ] = { 162 'file': file, 163 'data': data 164 }; 165 count += 1; 166 debug( 'Read %d of %d files.', count, len ); 167 if ( count === len ) { 168 return done(); 169 } 170 } 171 172 return onRead; 173 } 174 175 /** 176 * Callback invoked upon completion. 177 * 178 * @private 179 * @param {Error} [error] - error object 180 * @returns {void} 181 */ 182 function done( error ) { 183 clearPending( pending ); 184 if ( error ) { 185 errFLG = true; 186 return cb( error ); 187 } 188 debug( 'Successfully read all files.' ); 189 cb( null, results ); 190 } 191 } 192 193 194 // EXPORTS // 195 196 module.exports = readFileList;