I was cleaning up my code and ran into a little trouble with callbacks, specifically getting the correct values to output when a callback is called. Could some explain to me why the following code spits out something that I'm not expecting and a possible solution without having to put in another parameter of i to the run() function, or is passing in i to know my index upon calling the callback the only way to do this?
for (var i in dls) {
run(dls[i][0], dls[i][1], function(isTrue){
if (isTrue) {
// Do true stuff here
} else {
console.log("Value is: " + dls[i][3])
}
});
}
Calling run() actually has the correct inputs inside, but upon that function calling the callback and going into the else statement, dls[i][3] spits out the same value i times.
I've tried putting different scopes around (run()) and such but to no avail and can't seem to wrap my head around this.
Thanks
EDIT:
If I wanted to split it up into a separate function, how would I do it?
var run = function(cb){
setTimeout(function() {
cb(false)
}, 3000);
}
for (var i in dls) {
run(dls[i][0], dls[i][1], (function(index) {
return extraction
})(i));
}
function extraction(isTrue){
if (isTrue) {
// stuff
} else {
console.log("Nothing changed in " + dls[i][3])
}
}
Here dls[i][3] is still incorrect and prints the same value 3 times.
You have fallen into the traditional "loop trap"
When it comes time for your callback to run i is now a different value.
What you can do is cache that value in another wrapper function:
for (var i in dls) {
run(dls[i][0], dls[i][1], (function (currentIndex) {
return function(isTrue){
if (isTrue) {
// Do true stuff here
} else {
console.log("Value is: " + dls[currentIndex][3])
}
};
})(i));
}
In regards to the edit / second question, assuming this is what you wanted to do:
// note that I changed the function signature of `run`
var run = function(val1, val2, cb) {
setTimeout(function() {
cb(false);
}, 3000);
};
// note the `wrapper` here
for (var i in dls) {
run(dls[i][0], dls[i][1], wrapper(i));
}
// this is the same as what the IIFE is doing,
// just with an external function instead
function wrapper(scopedIndex) {
// return a function to be used as the callback for `run`
return function extraction(isTrue) {
if (isTrue) {
// stuff
}
else {
// use the scoped index here
console.log("Nothing changed in " + dls[scopedIndex][3]);
}
}
}
Take a look at function makeExitCallback(i) in the other linked question, as well. It directly relates to what's going on here.
You should post what's in dls as well, just to make it easier to run your snippets locally.
Related
I want to run code if another code snippet has run.
IF this code runs
(function() {
// Code runs here
})();
THEN run this code also
//This code
Example
if (condition) {
block of code to be executed if the condition is true
}
http://www.w3schools.com/js/js_if_else.asp
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/if...else
This won't seem to work?
if ((function() {// Code runs here})();)
{
//This code
}
You should use return statement otherwise the IIFE would return undefined and thus it would be equivalent to false statement.
if (
(function() {
// Code runs here
return true;
})();
){
//This code
}
Use this
var functionName = (function() {
var didRun = false;
// This function will be executed only once, no matter how many times
// it is called.
function functionName() {
// Your code goes here
}
return function() {
if (didRun) {
return;
}
didRun = true;
return foo.apply(this, arguments);
}
})();
and check, when the function didRun, then execute your core
The IIFE seems superfluous to me here - just use a named function the simple way and keep it straightforward. If anyone can give me a use for the IIFE as the conditional expression in an If ... else, please comment - I would love to understand what I might be missing:
function odd(num) {
return num % 2;
}
// Use Dev Tools Console (F12) to see output
function logOddEven(num) {
if (odd(num)) {
console.log(num + ' is odd');
} else {
console.log(num + ' is even');
}
}
logOddEven(0);
logOddEven(1);
logOddEven(2);
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
I'm relatively new to coding in JavaScript, and I've came across a problem. I like to nest functions to keep things orderly, but how would I exit from a parent function from inside a child function?
example:
function foo1() {
function foo2() {
//return foo1() and foo2()?
}
foo2();
}
See update under the fold
You can't. You can only return from the child function, and then return from the parent function.
I should note that in your example, nothing ever calls foo2 (As of your edit, something does). Let's look at a more real example (and one that comes up a lot): Let's say we want know if an array contains an entry matching some criterion. A first stab might be:
function doesArrayContainEntry(someArray) {
someArray.forEach(function(entry) {
if (entryMatchesCondition(entry)) {
return true; // Yes it does <-- This is wrong
}
});
return false; // No it doesn't
}
You can't directly do that. Instead, you have to return from your anonymous iterator function in a way to stop the forEach loop. Since forEach doesn't offer a way to do that, you use some, which does:
function doesArrayContainEntry(someArray) {
return someArray.some(function(entry) {
if (entryMatchesCondition(entry)) {
return true; // Yes it does
}
});
}
some returns true (and stops looping) if any call to the iterator function returns true; it returns false if no call to the iterator returned true.
Again, that's just one common example.
You've referred to setInterval below, which tells me that you're almost certainly doing this in a browser environment.
If so, your play function almost certainly has already returned by the time you want to do what you're talking about, assuming the game has any interaction with the user other than alert and confirm. This is because of the asynchronous nature of the environment.
For example:
function play() {
var health = 100;
function handleEvent() {
// Handle the event, impacting health
if (health < 0 {
// Here's where you probably wanted to call die()
}
}
hookUpSomeEvent(handleEvent);
}
The thing is, that play will run and return almost immediately. Then the browser waits for the event you hooked up to occur, and if it does, it triggers the code in handleEvent. But play has long-since returned.
Make a note whether the parent function should also return.
function foo1() {
bool shouldReturn = false;
function foo2() {
shouldReturn = true; // put some logic here to tell if foo1() should also return
return;
}
if (shouldReturn) {
return;
} else {
// continue
}
}
It only says that you can't return the parent function in the child function, but we can do a callback and make it happen.
function foo1(cb = () => null) {
function foo2() {
cb();
}
foo2();
}
foo1(() => {
// do something
});
We can use Promises for this:
const fun1 = async () => {
const shouldReturn = await new Promise((resolve, reject) => {
// in-game logic...
resolve(true)
})
if(shouldReturn) return;
}
if you wanna return from the parent function, then just resolve with true
Based on your comment, something like this might work as a main game loop.
function play() {
var stillPlaying = true;
while(stillPlaying) {
... play game ...
stillPlaying = false; // set this when some condition has determined you are done
}
}
I've written the code below to act as a simple slideshow in the header of a clients website. Now, the only problem is when I put it into a for or a while loop it gets to the end of the first loop and then stops.
I've tried using calling togglinga() in the last callback, I've tried, wrapping the whole thing in a for and while loop, I've tried creating a different function that calls this one and then using that same function name in the final call back but get the same result everytime. I'd really appreciate someone casting their eye over this to see if they can see anything I can't....
function triggerAnimation(){
$("#logoareacontainer").delay(15000).fadeOut(3000, function() {
$("#title-1").fadeIn(0).delay(0, function() {
$("#cdsl-1-1").fadeIn(1000).delay(2000).fadeOut(0, function(){
$("#cdsl-1-2").fadeIn(0).delay(2000).fadeOut(0, function(){
$("#logoareacontainer").fadeIn(1000).css({display:'block'})
})
})
})
})
}
Much shorter and easier if you break this into functions that can be called in a cyclical manner.
Note that .delay() doesn't accept a callback function, which was a big part of the problem.
Here's a demo: http://jsfiddle.net/kjaHZ/
// for each "title-", keep track of how many "cdsl-"s there are
var titles = [null, 4, 2, 2, 2, 1, 1];
start();
// start it off
function start() {
$("#logoareacontainer").delay(1500).fadeOut(3000, function () {
doTitle(1);
});
}
// this starts a "title-" section
function doTitle(i) {
if (i < titles.length) {
// do the "title-" for the given "i" variable
$("#title-" + i).fadeIn(0, function () {
// after fading in, do the "cdsl-" ids
doCDSL(i, 1);
});
} else {
// or if "i" is >= titles.length, we're done
$("#logoareacontainer").fadeIn(1000).css({display:'block'});
}
}
// this starts a "cdsl-" section
function doCDSL(i, j) {
$("#cdsl-" + i + "-" + j).fadeIn(1000)
.delay(2000)
.fadeOut(0, function () {
if (j < titles[i]) {
// move to the next "cdsl-"
doCDSL(i, j+1);
} else {
// or do the next "title-"
$("#title-" + i).fadeOut(1000).css({display:'none'})
doTitle(i+1);
}
})
}
although your code is pretty awfull here u are :) u missed ()
function togglinga(){ triggerAnimation(); };
You can't put the code in a loop, because it is asynchronous. The loop would just start all the animations at once, because the outermost call won't wait until all the animations are complete.
At the innermost level, just call triggerAnimation to make it restart.
I have a recursive function which does a sort of tree process where each call may call itself multiple times, I don't have any way of knowing how deep or wide it is. How do I run a callback once the entire process has been completed?
I'm thinking of having some sort of object to pass about to do a count but not quite cracked it yet, i'm wondering if there is a known best/better way of doing this.
You could do something like:
function recurseTree(arg, callback) {
var recurse = function(a) {
if (someCondition) {
recurse(a);
}
};
recurse(arg);
callback();
}
All of your actual recursive logic will go in the recurse function, and the callback will be called only after all recursion is finished.
EDIT:
Here is a simple implementation
function recursiveAlert(x, callback) {
var recurse = function(y) {
alert(y);
if (y < 3) {
recurse(y + 1);
}
}
recurse(x);
callback();
}
recursiveAlert(0, function() { alert('done'); });
what I needed to do is count the number of paths in each tree before calling the callback e.g.:
myFunction: function(tree) {
var count = 0;
finishCallback = function() {
if (--count === 0){
callback();
};
};
recursion = function(subTree) {
count = tree.paths.length;
_.each(subTree.path, function(route) {
count += subFolder.fileRefs.length;
recursion(route, function() {
finishCallback();
});
});
};
recursion(tree);
}
Perhaps the count should not be inside myFunction but recursion should have its own count, however this works. (i've not tested this example)