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