So, I understand callback functions to a degree. For instance, I completely understand this:
function finalGuy(x) {
alert("Final Number: " + x);
}
function secondGuy(x, callback) {
x = x - Math.sqrt(x);
callback(x);
}
function firstGuy(callback) {
var x = parseInt(prompt("Enter a number"));
x *= x;
secondGuy(x, callback);
}
firstGuy(finalGuy);
However, when faced with something like this I can't seem to grasp it.
a(function () {
b(function () {
c()
})
});
Can someone please translate the first sequence of callbacks into a working example like the second one? Specifically, passing one result to the next callback similarly to how I did it in the first example.
These two produce the same result
1.
function acceptCallback(callback) {
callback();
}
function callback() {
console.log('callback invoked');
}
acceptCallback(callback); // => 'callback invoked'
2.
function acceptCallback(callback) {
callback();
}
acceptCallback(function() {
console.log('callback invoked');
});
In the first example you pass a function declaration, in the second example you pass an anonymous function
3. Performing operations in the scope of a callback to be passed to another callback, aka "callback hell"
Nothing special here, it's the same syntax as the first two examples. This is difficult to read for anyone.
function first(callback) {
callback('from first');
}
function second(callback) {
callback('from second');
}
function third(callback) {
callback('from third');
}
function fourth(n, callback) {
callback(n * 10);
}
first(function(fromFirst) {
var a = 5;
console.log(fromFirst); // 'from first'
second(function(fromSecond) {
console.log(fromSecond); // 'from second'
third(function(fromThird) {
console.log(fromThird); // 'from third'
var b = a + 5; // 10
fourth(b, function(fromFouth) {
console.log(a, b, fromFourth); // 5, 10, 100
})
});
});
});
Related
Let's take this example:
function foo(callback){
callback();
baz();
}
function baz() {
console.log('Hello from baz');
}
foo(baz);
It looks like they are doing the same task. Or callback and calling a function inside another function are the same thing?
Except you aren't using the callback the way callbacks are supposed to be used. Callbacks are supposed to get the interim results from a previous function. Here's an example
function add (a, b) {
return a + b;
}
function subtract (a, b) {
return a - b;
}
This is the callback
function doLater (val) {
if (val > 0) {
console.log("positive");
} else if (val ==) {
console.log("zero");
} else console.log ("negative");
}
function doOp (a, b, mathOp, callback) {
var res = mathOp(a,b);
callback(res);
}
doOp (2,3, add, doLater);
vs.
doOp (2,3,subtract, doLater);
Basically, they are doing the same, but callbacks are very useful because you can call the same function with many different callbacks. That makes your foo function more reusable.
In your example, both approaches will do the same.
However, you can pass function as parameters, that are not in the functions scope.
E.g.
function foo(callbackFn){
callbackFn();
// function test is undefined here, since it was declared in another scope
}
function bar(){
// test is only available in bar's scope
function test(){
console.log("test");
}
foo(bar);
}
for example i have this program calling functions some with set time outs, some function work just calling them (setTimeout(this.siguienteNivel, 1500); but others needed (the last ones of the code) an arrow function, (​setTimeout(() =>​this.iluminarSecuenciaFinal();}, 1000 * i);) if the arrow function is missing the function does not trigger in these specific cases, i dont understand why this differentiator, the program is in a class, i dont know if that makes a difference
this is a piece, the complete code is this if needed:
https://github.com/moorooba/simon/blob/master/index.html
> elegirColor(ev) {
const nombreColor = ev.target.dataset.color;
const numeroColor = this.transformarColorANumero(nombreColor);
this.iluminarColor(nombreColor);
if (numeroColor === this.secuencia[this.subnivel]) {
this.subnivel++;
if (this.subnivel === this.nivel) {
this.nivel++;
this.eliminarEventosClick();
if (this.nivel === ULTIMO_NIVEL + 1) {
this.ganador();
} else {
setTimeout(this.siguienteNivel, 1500);
}
}
} else {
this.perdio();
}
}
// ganoElJuego() {
// swal("Platzi", "Felicitaciones, ganaste el juego!", "success").then(
// this.inicializar
// );
// }
// perdioElJuego() {
// swal("Platzi", "Lo lamentamos, perdiste :(", "error").then(() => {
// this.eliminarEventosClick();
// this.inicializar();
// });
// }
perdio() {
loser.classList.remove("hide");
setTimeout(this.startos, 1000);
this.eliminarEventosClick();
}
startos() {
loser.classList.add("hide");
boton.classList.remove("hide");
}
ganador() {
console.log("diparo");
this.eliminarEventosClick();
for (let i = 0; i < 4; i++) {
// setTimeout(this.iluminarSecuenciaFinal, 1000 * i);
setTimeout(() => {
this.iluminarSecuenciaFinal();
}, 1000 * i);
}
// setTimeout(this.ganoJuego, 4000);
setTimeout(() => {
this.ganoJuego();
}, 4000);
}
ganoJuego() {
console.log("ganojuego");
winner.classList.remove("hide");
// setTimeout(this.restart, 1500);
setTimeout(() => {
this.restart();
}, 1500);
}
You have to understand how this works in JavaScript:
class Foo {
bar = 'bar';
log() {
console.log(this.bar);
}
method() {
setTimeout(this.log);
setTimeout(() => this.log());
}
}
const obj = new Foo;
obj.method();
In this example, (which represents the relevant part of your code) the method function tries to call log in two different ways.
In the first one, it passes a reference to this.log as a parameter of setTimeout. The setTimeout function receives a function as a parameter and it has no idea of the context in which it has been declared, it's as if you extracted it from the class. In that case this is being evaluated when the function executes and it evaluates to window. The result is undefined because window.bar is undefined.
In the second one, it passes an arrow function whose particularity is to retain this when it is declared. When it executes, this refers to the instance of the class so the result is the same as if you called obj.log(). The result is 'bar' in that case.
I have been trying to learn about callBack hell, and was trying to replicate and then change it into promises. Here is my code, its saying on execution cb is not a function
What i am missing here.
var t = addTwo(function(a, b) {
console.log(a * b);
divideTwo(function() {
console.log("arshita");
})
})
function addTwo(cb) {
cb(3, 4);
}
function divideTwo(cb1) {
}
addTwo();
You have an error in your code
var t = addTwo(function(a, b) {
console.log(a * b);
divideTwo(function() {
console.log("arshita");
})
})
function addTwo(cb) {
cb(3, 4);
}
function divideTwo(cb1) {
}
addTwo((a, b) => a + b); // <----- Here you passed nothing, though `addTwo`
// expected a callback. I wrote an example callback that sums `a` and `b`
Update
If you want to see what callback hell looks like, then have a look at this (a simple, only 3-level callback hell):
function init(msg, cb) {
alert(msg)
cb()
}
function running(msg, cb) {
alert(msg)
cb()
}
function finish(msg) {
alert(msg)
}
// The below code may not be considered as "callback hell" yet, but add few more iterations and it definitely will become a hell
init('program started!', function() {
running('program is running', function() {
finish('program shut downs')
})
})
This is the simplified version of my problem:
var callback_one = function (result_from_web_service) {
console.log('callback one');
};
var callback_two = function (result_from_web_service) {
console.log('callback two');
};
// the async_calls are library calls that i don't own or modify
var x = function () {
console.log('x is called');
async_call_one(callback_one);
async_call_two(callback_two);
};
var y = function () {
console.log('y is called');
};
Test:
x();
y();
// prints: 'x is called', 'y is called', 'callback one', 'callback two'
// what i need: 'x is called', 'callback one', 'callback two', 'y is called'
What I did to accomplish this was calling y() inside call_back_two:
var callback_two = function (result_from_web_service) {
console.log('callback two');
y();
};
But now my use case requires that y be called outside of the callback (will be called by the user of my code). i.e. the calling of x() and y() be independent in a way that the calling of x() doesn't end up calling y(). y() should be called independently but only if x() and its callbacks are processed. think of x() as like creating an object in java and y() as a method you would call whenever you want.
//.. some code, x() does some kind of initialization...
x();
// OPTIONALLY call y(), but make sure x() and its callbacks are processed
y();
I tried the below but doesn't work.
$.when(x()).then(y());
Thanks,
The only way to make something like this work properly is to make y asynchronous. Basically, y internally waits for x to complete before executing its own code. This is similar to things like domready or onload which waits for other things to happen before executing their own logic.
There are two ways to accomplish this. The first, simplest and most naive way is setTimeout polling. Make x set a variable or attribute and check that before executing:
function y () {
if (x.ready) {
/* do what you need to do here */
}
else {
setTimeout(y,100);
}
}
The second method is to create virtual events or promises. Not all promise libraries support using a promise that has already expired (my own homemade one does) so you may need to write your own control flow to handle that case. For this you need to rewrite x to support an event or promise-like api:
var x = (function () {
var async_calls = 2;
var callback;
f = function () {
console.log('x is called');
async_call_one(function(){
async_calls --;
if (async_calls == 0 && callback) callback();
callback_one();
});
async_call_two(function(){
async_calls --;
if (async_calls == 0 && callback) callback();
callback_two();
});
}
f.loaded = function (loaded_callback) {
callback = loaded_callback;
if (async_calls == 0) callback();
}
return f;
})();
Now in y you can use the x.loaded function to execute your code when x is loaded:
function y () {
x.loaded(function(){
/* do what you need to do here */
});
}
Of course, this has the problem of making y asynchronous. Therefore if your users expect to write something like:
y();
a();
b();
then a and b may or may not execute before y. To solve this you need to make y accept a callback so that you users can control their program flow:
function y (callback) {
if (x.ready) {
/* do what you need to do here */
callback();
}
else {
setTimeout(function(){y(callback)},100);
}
}
or:
function y (callback) {
x.loaded(function(){
/* do what you need to do here */
callback();
});
}
So they'd have to use y like this:
y(function(){
a();
b();
});
Alternatively you can make y return a promise if you prefer that style.
A little bit down the road to spaghetti code but you could wrap up the two callbacks together by setting variables they both can see and only call y() when they both return.
for example
var finished = false;
var aync_call_one = function () {
//stuff
if (finished) {
y();
} else {
finished = true;
}
}
var aync_call_two = function () {
//stuff
if (finished) {
y();
} else {
finished = true;
}
}
However if you have to use jquery promises, you need to return a promise object to use $.when,
var deferred = $.Deferred();
return deferred.promise();
and inside the asyncs you need to do
deferred.resolve();
though that would only work for one of your async calls so you would need another $.when inside the first function, so it can get sloppy quick.
I have the following two functions:
var abc;
function updateNum() {
abc=0;
g.dbm.transaction("leagues").objectStore("leagues").openCursor(null, "prev").onsuccess = function (event) {
var teams, i;
team.filter({
attrs: ["tid", "abbrev", "region", "name", "cid"],
seasonAttrs: ["winp", "playoffRoundsWon"],
season: g.season
}, function (teams) {
// Sort teams by playoffs and winp, for first round
teams.sort(function (a, b) {
if (a.playoffRoundsWon < b.playoffRoundsWon) {
return -1;
}
if (a.playoffRoundsWon > b.playoffRoundsWon) {
return 1;
}
return a.winp - b.winp;
});
abc+=1;
});
};
}
function getNum() {
return abc;
}
What I am trying to do is update the variable abc inside the callback function and then return it. I do this by first calling the updateNum() function in another file. Then I assign a variable to the value of getNum()
Here is how a sample code would look like:
myFile.updateNum();
var number = myFile.getNum();
I am currently unable to return the updated value of num. number keeps returning 0 (the default value) instead of the newly updated value (which is 1).
How can I get it to show an updated value? Please let me know if I need to add any more information.
Well, if updateNum is async, it would have to take a callback as argument so that you can be notified when the number was updated.
E.g.
var num = 0;
function updateNumAsync(callback) {
setTimeout(function () {
num = 1;
callback && callback(num); //call the callback if provided
}, 500);
}
updateNumAsync(function (num) {
console.log(num); //updated num
});
Here is a general pattern for using an asynchronous function with a callback to pass the asynchronous results around. What is team.filter? You will need to design your code such that the asynchronous portion calls a callback() function that was passed to the enclosing function.
If filtering gives you problems you may want to look at https://github.com/caolan/async#filterarr-iterator-callback
(function main(){
getNum(function(err, abc){
console.log('thx for playing '+abc)
});
})();
function getNum(anotherCallback) {
// Whatever code relies on the result of an asynchronous function must be
// placed inside the callback function
countTeams(function(abc){
console.log('countTeams completed, abc='+abc);
var err = null;
anotherCallback(err, abc);
});
};
function countTeams(callback){
var abc=0;
g.dbm.transaction("leagues").objectStore("leagues").openCursor(null, "prev").onsuccess = function (event) {
var teams, i;
// I don't know what this filter function does, I am assuming it's synchronous
team.filter({
attrs: ["tid", "abbrev", "region", "name", "cid"],
seasonAttrs: ["winp", "playoffRoundsWon"],
season: g.season
}, function (teams) {
// Sort teams by playoffs and winp, for first round
teams.sort(function (a, b) {
if (a.playoffRoundsWon < b.playoffRoundsWon) {
return -1;
}
if (a.playoffRoundsWon > b.playoffRoundsWon) {
return 1;
}
return a.winp - b.winp;
});
abc+=1;
});
return callback(abc); // 0 or n depending on what team.filter does
};
};