time-to-botec

Benchmark sampling in different programming languages
Log | Files | Refs | README

limit.js (4342B)


      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 
     25 
     26 // VARIABLES //
     27 
     28 var debug = logger( 'bifurcate-by-async:limit' );
     29 
     30 
     31 // MAIN //
     32 
     33 /**
     34 * Invokes a predicate function once for each element in a collection, limiting the number of concurrently pending functions.
     35 *
     36 * ## Notes
     37 *
     38 * -   We need to cache the collection value to prevent the edge case where, during the invocation of the predicate function, the element at index `i` is swapped for some other value. For some, that might be a feature; here, we take the stance that one should be less clever.
     39 *
     40 *
     41 * @private
     42 * @param {Collection} collection - input collection
     43 * @param {Options} opts - function options
     44 * @param {*} [opts.thisArg] - execution context
     45 * @param {PositiveInteger} [opts.limit] - maximum number of pending function invocations
     46 * @param {string} [options.returns] - output format
     47 * @param {Function} predicate - predicate function
     48 * @param {Callback} done - function to invoke upon completion or upon encountering an error
     49 * @returns {void}
     50 */
     51 function limit( collection, opts, predicate, done ) {
     52 	var maxIndex;
     53 	var count;
     54 	var flg;
     55 	var lim;
     56 	var len;
     57 	var idx;
     58 	var out;
     59 	var i;
     60 
     61 	len = collection.length;
     62 	debug( 'Collection length: %d', len );
     63 
     64 	out = [];
     65 	if ( len === 0 ) {
     66 		debug( 'Finished processing a collection.' );
     67 		return done( null, out );
     68 	}
     69 	out.push( [], [] );
     70 	if ( len < opts.limit ) {
     71 		lim = len;
     72 	} else {
     73 		lim = opts.limit;
     74 	}
     75 	debug( 'Concurrency limit: %d', lim );
     76 	debug( 'Number of arguments: %d', predicate.length );
     77 
     78 	maxIndex = len - 1;
     79 	count = 0;
     80 	idx = -1;
     81 	for ( i = 0; i < lim; i++ ) {
     82 		// This guard is necessary to protect against synchronous functions which exhaust all collection elements...
     83 		if ( idx < maxIndex ) {
     84 			next(); // eslint-disable-line callback-return
     85 		}
     86 	}
     87 	/**
     88 	* Callback to invoke a provided function for the next element in a collection.
     89 	*
     90 	* @private
     91 	*/
     92 	function next() {
     93 		var v;
     94 		var j;
     95 
     96 		idx += 1;
     97 		j = idx;
     98 		v = collection[ j ];
     99 
    100 		debug( 'Collection element %d: %s.', j, JSON.stringify( v ) );
    101 		if ( predicate.length === 2 ) {
    102 			predicate.call( opts.thisArg, v, cb );
    103 		} else if ( predicate.length === 3 ) {
    104 			predicate.call( opts.thisArg, v, j, cb );
    105 		} else {
    106 			predicate.call( opts.thisArg, v, j, collection, cb );
    107 		}
    108 		/**
    109 		* Callback invoked once a provided function finishes processing a collection element.
    110 		*
    111 		* @private
    112 		* @param {*} [error] - error
    113 		* @param {*} [bool] - group indicator
    114 		* @returns {void}
    115 		*/
    116 		function cb( error, bool ) {
    117 			if ( flg ) {
    118 				// Prevent further processing of collection elements:
    119 				return;
    120 			}
    121 			if ( error ) {
    122 				flg = true;
    123 				return clbk( error );
    124 			}
    125 			debug( 'Collection element %d group: %s.', j, ( bool ) ? '0' : '1' );
    126 
    127 			// Default is to return values...
    128 			if ( opts.returns === 'indices' ) {
    129 				if ( bool ) {
    130 					out[ 0 ].push( j );
    131 				} else {
    132 					out[ 1 ].push( j );
    133 				}
    134 			} else if ( opts.returns === '*' ) {
    135 				if ( bool ) {
    136 					out[ 0 ].push( [ j, v ] );
    137 				} else {
    138 					out[ 1 ].push( [ j, v ] );
    139 				}
    140 			} else if ( bool ) {
    141 				out[ 0 ].push( v );
    142 			} else {
    143 				out[ 1 ].push( v );
    144 			}
    145 			clbk();
    146 		}
    147 	}
    148 
    149 	/**
    150 	* Callback invoked once ready to process the next collection element.
    151 	*
    152 	* @private
    153 	* @param {*} [error] - error
    154 	* @returns {void}
    155 	*/
    156 	function clbk( error ) {
    157 		if ( error ) {
    158 			debug( 'Encountered an error: %s', error.message );
    159 			return done( error );
    160 		}
    161 		count += 1;
    162 		debug( 'Processed %d of %d collection elements.', count, len );
    163 		if ( idx < maxIndex ) {
    164 			return next();
    165 		}
    166 		if ( count === len ) {
    167 			debug( 'Finished processing a collection.' );
    168 			return done( null, out );
    169 		}
    170 	}
    171 }
    172 
    173 
    174 // EXPORTS //
    175 
    176 module.exports = limit;