I'm trying to solve this async problem. I got the problem working.
However, after calling "done" callback it's throwing an error. I don't know why.
Issue:
I want to print the task number once "Done" is invoked.
But it throws an
error saying TypeError: "callback" argument must be a function
Problem:
A "TaskRunner" Constructor takes one argument, "concurrency", and
exposes one method "push" on its prototype. The "push" method takes
one argument "task" which is a "function"
Passing a task to a push method of a TaskRunner instance should
immediately execute (call/run/invoke) the task, unless the number
of currently running tasks exceeds the concurrency limit.
function TaskRunner(concurrency) {
this.count = concurrency;
this.queue = [];
}
TaskRunner.prototype.push = function(task) {
if (this.count === 0) {
this.queue.push(task);
} else {
this.invoke(task);
}
}
TaskRunner.prototype.invoke = function(task) {
task.call(null, this.done.bind(this));
this.count--;
}
TaskRunner.prototype.done = function(num) {
console.log(`After Executing done: ${num}`)
this.count++;
this.execute();
}
TaskRunner.prototype.execute = function() {
if (this.queue.length > 0) {
var task = this.queue.shift();
this.invoke(task);
}
}
function factory(num) {
return function exampleSimpleTask(done) {
this.num = num;
console.log("task", "Before " + new Date().getTime());
setTimeout(done(num), 2000);
}
}
var r = new TaskRunner(2);
r.push(factory(1));
r.push(factory(2));
r.push(factory(3));
EDIT: For some reason, on jsfiddle it runs fine but when I run the same code on my local it fails.
Please help.
You are passing the setTimeout the result of you function:
setTimeout(done(num), 2000);
This will call done(num) immediately and setTimeout will try to call whatever done() returned as through it were a function.
You should pass it a function that it can call instead:
setTimeout(() => done(num), 2000);
or [as #JaromandaX points out in the comment] you can take advantage of the options third argument of setTimeOut which will be passed into the callback function:
setTimeout(done, 2000, num);
this will call the function done and pass in num
Related
Got take-home assignment:
"You need to build a stub for fetch(url) function, which will fail n requests and starting from n+1 will fetch data successfully. Must be way to configure it passing number of requests to be failed (n, required) and optional parameter 'time to wait before resolve/reject'. Also must be the way to reset the request counter invoking fetch.reset(). Just as original fetch(), function should return Promise."
So, we need fetch-like function with a functionality mentioned above. Problem is with fetch.reset() method. Can't figure out how I can attach function to callback function.
So, no problem with all of these except for fetch.reset().
function outer (num, time) {
let count = 1;
return function fetchIt() {
return new Promise((resolve, reject) => {
if (count > num) {
count++;
setTimeout(()=>{resolve('ok');}, time * 1000) // here actual data will be returned/resolved
} else {
count++;
setTimeout(()=>{reject();}, time * 1000)
}
})
}
}
let newFetch = outer(2, 2);
newFetch().then(()=>console.log('ok'), ()=>console.log('not ok')); // 'not ok'
newFetch().then(()=>console.log('ok'), ()=>console.log('not ok')); // 'not ok'
newFetch().then(()=>console.log('ok'), ()=>console.log('not ok')); // 'ok'
Now, how can i make newFetch.reset() method to reset counter to 1?
Tried prototype - nope. I think problems are with accessing inner function from outer scope.
Plunk for this stuff:
http://plnkr.co/edit/FiXKyDJ1E2cv8LuUMxRM
Assign fetchIt to a variable before returning, then add the function on there:
function outer (num, time) {
...
let fetchIt = function fetchIt() {
...
}
fetchIt.reset = function reset() {
count = 1 // Or whatever you need to do
}
return fetchIt
}
I have written a basic job runner in javascript (utilizing some JQuery too, but that's another story for another day) and I came across this queer Issue:
The method I run to wait for all jobs to complete:
$.getAllProducts = function(callback){
$.getProductDetails('|ALL|', function(allProductsResult){ //intentionally
var objAllProducts = JSON.parse(JSON.parse(allProductsResult));
var objProductsBuiltUp = {};
var productLength = objAllProducts.length;
$.totalJobs(productLength);
var processed = 0;
$.completedJobs(processed);
$.each(objAllProducts, function(i,v){
$.getProductDetails(objAllProducts[i].ProductCode, function(result){
$.mergeShallow(objProductsBuiltUp, JSON.parse(JSON.parse(result)));
processed++;
$.completedJobs(processed);
});
});
$.wait(0, false, function(isDone){ //allow at least 50ms wait time, otherwise this confuses javascript into thinking there are no callbacks
if (isDone){
callback(objProductsBuiltUp.ProductComponents);
}
});
});
}
The handlers for the job
$.checkProgress = function() {
return $.jobs === $.completed;
}
$.totalJobs = function(total) {
$.jobs = total;
}
$.completedJobs = function(completed) {
$.completed = completed;
}
$.wait = function(timeout, debug, callback) {
setTimeout(function() {
if (debug) {
console.log($.completed + " / " + $.jobs + " = " + ($.completed / $.jobs * 100) + "%");
}
if ($.checkProgress() == false) {
$.wait(timeout, debug);
}
callback($.checkProgress()); // <-- complaining one
}, timeout);
}
This is the key-point code for my little job runner, other methods will call $.totalJobs() to set the amount of jobs that need to be performed (normally based on amount different calls need to be made to an API In my scenario), and $.completedJobs() - which is called when the payloads are returned in the API handler's callbacks
The issue is, when I set my "Waiter" to 50ms, I don't get any errors whatsoever, and the method performs as per expected.
When I set it to low values like 5ms, 1ms, 0ms, It tells me:
"xxxxx.helpers.js:48 Uncaught TypeError: callback is not a function"
Anyone have a wild-flung theory why this would occur? it is, afterall, only a glorified setTimeout.
(P.S. In response to why I use JQuery global methods and variables to store info is to make using Meteor easier on myself knowing it's loaded into 1 place - this is the platform I am developing on at the moment.)
EDIT was better for me to add the whole method where the callback is run
It looks like you're not passing a callback here:
$.wait = function(timeout, debug, callback) {
//code here
if ($.checkProgress() == false) {
$.wait(timeout, debug); // === $.wait(timeout, debug, undefined);
}
callback($.checkProgress()); // <-- complaining one
}, timeout);
so if $.checkProgress() is false, you're calling $.wait recursively only callback is undefined...
At first glance, I think what you wanted to write there was:
$.wait(timeout, debug, callback); // pass callback argument to inner call
But then obviously, you wouldn't want to invoke the callback multiple times:
$.wait = function(timeout, debug, callback) {
//code here
if ($.checkProgress() == false) {
$.wait(timeout, debug, callback);
} else {
callback($.checkProgress());
}
}, timeout);
The reason why the line you marked as "the complaining one" is in fact complaining is because it's the recursive call. $.checkProgress evaluated to false, the $.wait function is invoked (this time, callback is undefined), and that continues until $.checkProgress() === false evaluates to false. Then, callback (which is undefined) will get invoked in the inner call.
This issue started appearing when the interval was reduced down. That makes sense, because you're only recursively calling $.wait if the jobs hadn't been completed. The higher the timeout/interval, the greater the chance the jobs were completed the first time around.
By reducing the interval, you arrived at a point where $.wait got invoked before the jobs had finished, and you entered the $.checkProgress() === false branch, calling $.wait without passing the callback (essentially losing a reference to it).
By the time the jobs had completed, you were trying to invoke callbackm which was set to undefined.
In
if ($.checkProgress() == false) {
$.wait(timeout, debug);
}
you're not passing through the callback parameter, so in the "recursive" call it will be undefined and you're getting the exception you posted. Make it
if ($.checkProgress() == false) {
$.wait(timeout, debug, callback);
// ^^^^^^^^
}
The documentation for the _.throttle function states that:
Creates a throttled function that only invokes func at most once per
every wait milliseconds. The throttled function comes with a cancel
method to cancel delayed func invocations and a flush method to
immediately invoke them. Provide an options object to indicate whether
func should be invoked on the leading and/or trailing edge of the wait
timeout. The func is invoked with the last arguments provided to the
throttled function. Subsequent calls to the throttled function return
the result of the last func invocation
I'm interested in this line:
Subsequent calls to the throttled function return
the result of the last func invocation
I've tried:
var throttled = _.throttle(updateModelData, 1000);
service.on('change', function () {
throttled(5);
});
function updateModelData(data) {
// all calls here log 5's
console.log(data);
return data;
}
setTimeout(function() {
throttled(); // here updateModelData is executed with `undefined` value
}, 5000);
The problem is that throttled() triggers function without returning the data. How can I invoke it so that it returns last data?
EDIT:
According to source code, the value will be returned only if no pending function call exists isCalled === false:
function debounced() {
args = arguments;
stamp = now();
thisArg = this;
trailingCall = trailing && (timeoutId || !leading);
if (maxWait === false) {
var leadingCall = leading && !timeoutId;
} else {
if (!maxTimeoutId && !leading) {
lastCalled = stamp;
}
var remaining = maxWait - (stamp - lastCalled),
isCalled = remaining <= 0 || remaining > maxWait;
!!!!! HERE
if (isCalled) {
if (maxTimeoutId) {
maxTimeoutId = clearTimeout(maxTimeoutId);
}
lastCalled = stamp;
result = func.apply(thisArg, args);
}
else if (!maxTimeoutId) {
maxTimeoutId = setTimeout(maxDelayed, remaining);
}
}
...
return result;
}
So the following will work:
var throttled = _.throttle(updateModelData, 10000);
service.on('change', function () {
throttled(5);
});
function updateModelData(data) {
// all calls here log 5's
console.log(data);
return data;
}
setTimeout(function() {
throttled(); // returns 5
}, 15000);
The issue is that, when you have leading invocations (the default behavior for _.throttle), when you first call the throttled function (or first call it after after your delay time has passed) it immediately calls the underlying function, before returning anything.
That means that the "result of the last function invocation" might be the result of a function invocation that was caused by your current call to the throttled function. So your call to throttle() calls updateModelData() and then returns undefined, since updateModelData() returns undefined.
Here's some sample code that might clarify this:
var foo = (x) => x;
var leading = _.throttle(foo, DELAY, {leading: true, trailing: false}); //these are the default options for leading and trailing
var trailing = _.throttle(foo, DELAY, {leading: false, trailing: true});
leading(1); //Calls foo(1), returns 1
leading(2); //Doesn't call foo, returns 1,
leading(3); //Doesn't call foo, returns 1
trailing(1); //Doesn't call foo, returns undefined
trailing(2); //Doesn't call foo, returns undefined
//DELAY ms later
//foo(2) is called, due to the most recent call to bar2
leading(); //Calls foo(), returns undefined
leading(1); //Still returns undefined from above
trailing(); //Doesn't call foo, returns 2
trailing(1); //Doesn't call foo, returns 2
//Another DELAY ms later
leading("Whatever"); //Calls foo("Whatever"), returns "Whatever";
Here's a version of your JSFiddle that makes it slightly more obvious too.
Really, you shouldn't call a function just to get the last value returned by it, so I'd suggest you just manage the last value yourself and not rely on _.throttle to do it for you. For example:
var lastResultOfFoo;
var foo = function (x) {
lastResultOfFoo = x;
return x;
}
//OR (if you don't mind attaching arbitrary properties to functions)
var foo = function (x) {
foo.lastResult = x;
return x;
}
The following code works fine:
var throttled = _.throttle(updateModelData, 1000);
var i = 0;
function updateModelData(data) {
return data;
}
var interval = setInterval(function() {
console.log(throttled(i++));
if (i === 6) {
clearInterval(interval);
console.log('Last value: ' + throttled());
}
}, 2000);
Output:
0
1
2
3
4
5
"Last value: 5"
DEMO
I have a callback that gets tossed around a lot. I can't figure a way to keep it through all of functions.
function a(callback) {
callback();
}
var number = 0;
function b(callback) {
number++;
c(number, callback);
}
function c(number, callback) {
if (number != 2) {
a(function () {
b();
});
} else {
callback();
}
}
a(function() {
b(function() {
console.log('hi');
});
});
I need a way to get the outermost callback (the one with console.log) to the a() function and subsequently the b() function when I call the a() function in the c() function.
Let me know if this sounds like gibberish. I will try to clarify.
You are encountering a TypeError: undefined is not a function. This is because in c you're invoking b without passing any args so when number !== 2 you've lost your reference
I re-wrote your code and it ended up working
var number = 0;
function invoke(fn) { // do you really need this? just invoke directly
fn();
}
function count(fn) {
++number;
limit(number, fn);
}
function limit(num, fn) {
if (num < 2) // this is much safer
invoke(function () {
count(fn); // passing an arg here
});
else
fn();
}
invoke(function () {
count(function () {
console.log('hello world');
});
});
You're getting dangerously close to what is known as 'callback hell'. You'll want to avoid that if you can. Since I'm not 100% sure why you need to pass around so many callback functions, you can achieve what you're looking for by doing the following:
function increment(number, callback, callback2) {
console.log('Incrementing number ' + number + ' by 1');
number++;
callback(number, increment, callback2);
}
function verify(number, callback, callback2) {
if (number < 2) {
callback(number, verify, callback2);
} else {
callback2();
}
}
var number = 0;
increment(number, verify, function () {
console.log('Hello world!');
});
You'll notice that I renamed some functions and excluded your a() function since it did nothing but execute the callback function passed to it. Unless that function is to serve another purpose, it is useless. You also don't need to create an anonymous function every time you are passing a callback. To pass a previously defined function as a parameter or callback, simply pass the function name without the (). If you are passing an undefined function as a callback, you will need to create the anonymous function which is what is being done with the console.log() callback.
Here's a jsfiddle. You can open your console log and see what it's doing.
jsfiddle
Please also be sure to read up on callback hell and how to design your code in a way that it can be avoided. Call back hell
How can I use setTimeout if I want to return a value
$.each(pCodes, function(index, pCode) {
setTimeout(func(parm1), 2000);
});
function func(in)
{
var value = 999;
return value;
}
First of all, your call to setTimeout is wrong. You are calling the function func and then using the result in the setTimeout method. Your code is equivalent to:
$.each(pCodes, function(index, pCode) {
var temp = func(parm1);
setTimeout(temp, 2000);
});
As func returns 999, you will be doing setTimeout(999, 2000), which of course doesn't make sense. To call a function that takes a parameter from setTimeout you need a function that makes that function call:
$.each(pCodes, function(index, pCode) {
setTimeout(function() { func(parm1); }, 2000);
});
To handle the return value from func is a bit more complicated. As it's called later on, you have to handle the return value later on. Usually that is done with a callback method that is called when the return value is available:
var callback = function(value) {
// Here you can use the value.
};
$.each(pCodes, function(index, pCode) {
setTimeout(function() { func(parm1, callback); }, 2000);
});
function func(in, callback) {
var value = 999;
callback(value);
}
First of all, make sure you pass to setTimeout a function, in your example you passed undefined to it, as you func(param1) executes func directly. What you want is something like this:
setTimeout(function() { func(parm1); }, 2000);
And for 'returning' the value: Use some kind of callback function that is executed with the value when timeout expired. Like so:
function callback(value) {
// doSomethingWithNewValue
}
$.each(pCodes, function(index, pCode) {
setTimeout(function() { func(parm1, callback); }, 2000);
});
function func(in, callback)
{
var value = 999;
callback(value);
}
This is the general pattern used in such scenario (see event driven programming).
change it to :
var defValue;
$.each(pCodes, function(index, pCode) {
setTimeout(function(){defValue=func(parm1)}, 2000);
});
this way you can use the defValue in your function to access the returned value
It's pretty ugly, but you can use output parameters, since js objects are pass by reference:
function a() {
var param1 = 42;
var result = {};
b(param1, result);
}
function b(val, output) {
something();
output.returned = 4;
}
Or, you can use a callback (the better option):
function a() {
var param1 = 42;
b(param1, function (newVal) {
something();
});
}
function b(val, callback) {
//something
callback(4);
}
By the way, your call to setTimeout is wrong. setTimeout receives a function as a first parameter, and a delay as a second - the first argument is still seen as regular javascript code, so it evaluates it, so your setTimeout call turns out to be like this:
setTimeout(999, 2000);
Since you're returning 999 from the function.
However, setTimeout can also receive a list of arguments after the second parameter, so it can be turned into this:
setTimeout(func, 2000, param1);