merge callback function result with call limit - javascript

Disclaimer: This was an interview question I was asked today. However, I do think there are real world use cases.
Supposedly, we have a function called helperLimit, it takes in 4 params. This function will merge list of results from multiple callback functions.
args: an array of argument (IDs)
limit: number of callback function can be fire at any given time
fn: a callback function that takes arg (ID) and a callback as param
done: a callback merges all async results from list of calls using each arg (ID)
I can come up with partial answer but I don't know how to handle the rate limit control portion of the question.
// This function works except the limit part
function helperLimit(args, limit, fn, done) {
const final = [];
let complete = args.length;
for (let currentIndex = 0; currentIndex < args.length; currentIndex++) {
fn(args[currentIndex], (result) => {
final[currentIndex] = result;
complete--;
if (complete === 0) {
done(final)
}
})
}
}
// simulate API calls
function getById(id, callback) {
const random = Math.floor(Math.random() * 1000) + 5;
setTimeout(() => {
callback({id, result: `result ${id}`});
}, random)
}
helperLimit([1,2,3,4,5], 2, getById, (result) => { console.log(result); })
Thank you in advance for providing helps.

To implement the limit feature, call the function at most limit times in the loop. Then in the callback that you have for fn, call fn again (once) until you have covered all calls:
function helperLimit(args, limit, fn, done) {
const final = [];
let stillToComplete = args.length;
limit = Math.min(limit, args.length);
let currentIndex = 0;
while (currentIndex < limit) perform(currentIndex++);
function perform(index) {
fn(args[index], (result) => {
final[index] = result;
stillToComplete--;
if (stillToComplete) {
if (currentIndex < args.length) perform(currentIndex++);
} else {
done(final);
}
})
}
}

Related

How to limit function calls in JS?

I need a function limitCalls (fn, maxCalls) that takes a function fn and returns a new function that can be called no more than the number of times specified in maxCalls. Test example:
it('limitCalls', () => {
const makeIncrement = () => {
let count = 0;
return () => {
count += 1;
return count;
};
};
const limitedIncrementA = limitCalls(makeIncrement(), 3);
expect(limitedIncrementA()).toBe(1);
expect(limitedIncrementA()).toBe(2);
expect(limitedIncrementA()).toBe(3);
expect(limitedIncrementA()).toBe(undefined);
expect(limitedIncrementA()).toBe(undefined);
const limitedIncrementB = limitCalls(makeIncrement(), 1);
expect(limitedIncrementB()).toBe(1);
expect(limitedIncrementB()).toBe(undefined);
expect(limitedIncrementB()).toBe(undefined);
});
I have:
var calls = 0;
export default function limitCalls(fn, maxCalls) {
if (calls >= maxCalls) {
return undefined;
}
calls += 1;
return fn();
}
And error is limitedIncrementA is not a function. Help me please to realise it.
Instead of conditionally returning a function, always return a function that conditionally executes the fn callback:
function limitCalls(fn, maxCalls) {
let count = 0;
return function(...args) {
return count++ < maxCalls ? fn(...args) : undefined;
}
}
const limited = limitCalls(console.log, 3);
limited('one');
limited('two');
limited('three');
limited('four');
In this snippet, limitedIncrementA isn't indeed a function. See this:
/* You're calling makeIncrement,
so you're passing its return to 'limitCalls'
*/
const limitedIncrementA = limitCalls(makeIncrement(), 3);
/* Here, considering that makeIncrement exists,
you're passing a reference to this functions,
which can be called inside 'limitCalls'
*/
const limitedIncrementB = limitCalls(makeIncrement, 3);
So, supposing that makeIncrement returns 1, 2, 3, ..., your current code is equivalent to:
limitCalls(1, 3);

Creating a Timestamped Object Array for Sampling Data in Javascript?

Goal is to push sampled data, as an object, onto an array, at a periodic interval and wait to log the new array out to the console once it is finalized.
I'm new to JS, so take it easy ;). I am likely making this more complicated than it needs to be. Thought it would be as simple as a setTimeout() in a for loop.
I have been able to generate the array two different ways, using IIFE with a setTimeout() also the setInterval() below. Not sure how to get the async await function working with an array push() method querying length. Maybe this is not a good approach?
class Sample {
constructor(tag, timeStamp) {
this.tag = tag;
this.timeStamp = Date.now();
}
}
function arrayGenerator(tag){
return sampleArr.push(new Sample(tag));
};
function setIntSample(callback, delay, iterations) {
var i = 0;
var intervalID = setInterval(function () {
callback(i);
if (++i === iterations) {
clearInterval(intervalID);
}
}, delay);
};
Above seems to work console.log()-ing the array as it is generated in the arrayGenerator() function. Below, no dice
function resolveAfterArrGeneration(){
return new Promise(resolve => {
arrLength = setIntSample(i => {arrayGenerator(i)}, 3000, 5)
if (arrLength === 5) {resolve();}
});
}
async function ans() {
var answer = await resolveAfterArrGeneration();
console.log(sampleArr);
}
ans();
The basic idea is to return a promise and resolve the promise when the setInterval has run enough iterations. You can do that in a single function with something like this (with extra console.logs to show the process):
class Sample {
constructor(tag, timeStamp) {
this.tag = tag;
this.timeStamp = Date.now();
}
}
function makeSamples(iterations, delay){
let samples = [], i = 0;
return new Promise(resolve => {
let intervalID = setInterval(function () {
console.log("pushing new sample")
samples.push(new Sample('tag: ' + i));
if (++i === iterations) {
console.log("finished resolving")
clearInterval(intervalID);
resolve(samples)
}
}, delay);
})
}
makeSamples(5, 1000).then(console.log)
I would isolate the delay part (the asynchronous) part and create a separate, generic function delay() for that. All the rest becomes simple then, using an async function and for loop:
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
class Sample {
constructor(tag, timeStamp) {
this.tag = tag;
this.timeStamp = Date.now();
}
}
async function setIntSample(callback, ms, iterations) {
const arr = [];
for (let i = 0; i < iterations; i++) {
if (i) await delay(ms); // don't delay first time
arr.push(callback(i));
}
return arr;
}
const newSample = (tag) => new Sample(tag)
console.log("wait for it....");
setIntSample(newSample, 1000, 5).then(console.log);
Another way I just got working with a generator function
function* simpleGenerator(){
var index = 0;
while (true)
yield {tag: index++, time: Date.now()}
}
var gen = simpleGenerator();
..with the corresponding push
arr.push(gen.next().value);

NodeJS: Parsing data sequentially and asynchronously

I am writing a NodeJS app that will load data from a database, parse it, and then save the parsed result to a different table in the database. Here is what I currently have:
parse(index, from, to) {
var collection = this.getCollectionName();
var interval = global.Settings.Parser.ParseInterval;
var promises = [];
console.log('%d - %d', from, from + interval);
for(from; from < to; from += interval) {
promises.push(new Promise((resolve, reject) => {
var scoped = from;
this.data.query(collection, {[index]: { $gte: from, $lte: from + interval}, (result) => {
for(var i = 0; i < result.length; i++)
this.sendToBuilder(result[i]);
resolve();
});
}));
}
promises.reduce((promise) => {
Promise.resolve()
});
}
The code seems to do what it should, but since the database query is asynchronous, it seems that out-of-order is a common occurrence. I do not want this to happen. I want each query and promise to execute sequentially to maintain the order of data.
I am trying the array.reduce() method to try and chain each promise to execute sequentially, but due to the nature of Promises it simply starts the promise and continues on, making them all fire simultaneously.
How can I ensure that it will execute sequentially? I don't mind delays between each promise as long as it doesn't block the actual thread.
Here's a working example in the spirit of your code using async/await:
function getData(reqId, collection, index, gte, lte) {
return new Promise((resolve, reject) => {
const delay = Math.floor(Math.random() * 2000) + 1;
console.log(`[getData ${reqId}] delay: ${delay}`);
const params = { collection, [index]: { $gte: gte, $lte: lte } };
const results = [0, 1, 2].map(r => `result ${reqId}.${r}`);
setTimeout(() => {
console.log(`[getData ${reqId}] this.data.query(${JSON.stringify(params)})`);
resolve(results);
}, delay);
});
}
async function parse(index, from, to) {
const collection = 'My Collection';
const interval = 10;
console.log(`Processing ${from} to ${to} by ${interval}:`);
for (from; from <= to; from += interval) {
const reqId = from;
console.log(`[parse] BEGIN ${reqId}`);
const results = await getData(reqId, collection, index, from, from + interval);
results.forEach(result => {
console.log(`[parse - awaited ${reqId}] this.sendToBuilder(${result})`);
});
console.log(`[parse] END ${reqId}`);
}
}
parse('idx', 200, 250);

How to write callback function inside for loop in node.js

I am trying to write a function inside a for loop. But the loop is not waiting until getting response for the function inside. How can I stop the loop until getting response for the function every time?
In this process the next loop is dependent on the response of the function.
My example code:
var a = function(data, callback) {
var d = 1;
for (var i = 0; i < data.length; i++) {
b(d, function(err, result) {
if (!err) {
d = result;
}
if ((i + 1) === data.length) {
callback(err, 'something');
}
});
}
}
var b = function(data, callback) {
var c = data + 1;
callback(null, c);
}
In this code the for loop is not waiting until it gets the response form the function b.
You can easily iterate your input array by calling a recursive function call within your callbacks:
'use strict';
const a = function(data, callback) {
let i = 0;
let sum = 0;
// loop function
function next() {
const x = data[i++];
if (!x) {
return callback(null, sum);
}
return b(x, (err, data) => {
sum += data;
next();
});
}
next(); // starts iterating
}
// multiplies data * 3
const b = function(data, callback) {
let c = data * 3;
callback(null, c);
}
a([1, 2, 3], (err, data) => {
console.log(data); // prints 18
});
Use Promises
Your code can easily be refactored to use Promises:
function a (data) {
let promise = Promise.resolve(1);
for (let i = 0; i < data.length; i++) {
promise = promise.then(b);
}
return promise.then(function (v) {
// v is the value computed by the promise chain
return 'something';
});
}
function b (data) {
// some async action that returns a promise
return Promise.resolve(data + 1);
}
Usage:
a(['a', 'b', 'c']).then(result => console.log(result)); // logs 'something'
Use async functions
Working with promises allows you to treat asynchronous code like synchronous code using async functions:
async function a (data) {
let v = 1;
for (let i = 0; i < data.length; i++) {
// looks like synchronous code, but is asynchronous
v = await b(v);
}
// v is the value computed by the asynchronous loop
return 'something';
}

array and asynchronous function callback

I got async function:
var func = function (arg, next) {
var milliseconds = 1000;
setTimeout(function(){
console.log (arg);
next()
} , milliseconds);
}
And array:
var arr = new Array();
arr.push (0);
arr.push (1);
console.log(arr);
I want to use func for every item of my array arr:
func(arr[0], function(){
func(arr[1], function(){
console.log("finish");
})
})
Ok for array consisted of 2 elements, but if I got array of 1000 elements how to use func for every item in arr?
How to do it in cycle?
var arrayFunc = function(array) {
if (array.length > 0) {
func(array[0], function() { arrayFunc(array.slice(1)); });
}
}
This will run your function with the first element in the array, and then have the continuation function take the rest of the array. So when it runs it will run the new first element in the array.
EDIT: here's a modified version that doesn't copy the array around:
var arrayFunc = function(array, index) {
if (index < array.length) {
func(array[index], function() {
var newI = index + 1;
arrayFunc(array, newI);
});
}
}
And just call it the first time with an index of 0.
While your approach is valid, it's not possible to use it if you have an uncertain number of calls, since every chain in your async command is hardcoded.
If you want to apply the same functionality on an array, it's best to provide a function that creates an internal function and applies the timeout on it's inner function:
var asyncArraySequence = function (array, callback, done){
var timeout = 1000, sequencer, index = 0;
// done is optional, but can be used if you want to have something
// that should be called after everything has been done
if(done === null || typeof done === "undefined")
done = function(){}
// set up the sequencer - it's similar to your `func`
sequencer = function(){
if(index === array.length) {
return done();
} else {
callback(array[index]);
index = index + 1;
setTimeout(sequencer, timeout);
}
};
setTimeout(sequencer, timeout);
}
var arr = [1,2,3];
asyncArraySequence(arr, function(val){console.log(val);});
A simple asynchronous loop:
function each(arr, iter, callback) {
var i = 0;
function step() {
if (i < arr.length)
iter(arr[i++], step);
else if (typeof callback == "function")
callback();
}
step();
}
Now use
each(arr, func);
You may try arr.map
var func = function (arg, i) {
var milliseconds = 1000;
setTimeout(function(){
console.log (arg);
}, milliseconds*i);
}
var arr = new Array();
arr.push (0);
arr.push (1);
arr.map(func);
Demo and Polyfill for older browsers.
Update : I thought the OP wants to loop through the array and call the callback function with each array item but I was probably wrong, so instead of deleting the answer I'm just keeping it here, maybe it would be helpful for someone else in future. This doesn't answer the current question.
Thanx, #Herms. Working solution:
var arrayFunc = function(array) {
if (array.length > 0) {
func(array[0], function() {arrayFunc(array.slice(1)); });
}
else
{
console.log("finish");
}
}
arrayFunc(arr);
A simple solution would be:
var fn = arr.reduceRight(function (a, b) {
return func.bind(null, b, a);
}, function() {
console.log('finish');
});
fn();
demo: http://jsbin.com/isuwac/2/
or if the order of func's parameters could be changed to receive the next callback as the first parameter, it could be as simple as:
['a', 'b', 'c'].reduceRight(func.bind.bind(func, null), function (){
console.log('finish');
})();
demo: http://jsbin.com/ucUZUBe/1/edit?js,console
You can loop through the array
for(var i = 0; i < arr.length; i++){
func(arr[i], function(){...});
}

Categories

Resources