please explain to me how this works. I'm new to nodejs and the way they set up their code is very complicated to me to properly understand..
I split the code that I don't understand into little snippets. You can find the whole code below.
callback(function() {
results.push(Array.prototype.slice.call(arguments));
next();
});
I don't understand the above snippet. It seems like this anonymous function becomes the next argument of the series anon function? Will the code in the anon function arguments execute?
function(next) { async(1, next); }
Which will execute first? The async function execution in the series functions or the next parameter execution?
Here I attached the full code:
function series(callbacks, last) {
var results = [];
function next() {
var callback = callbacks.shift();
if(callback) {
callback(function() {
results.push(Array.prototype.slice.call(arguments));
next();
});
} else {
last(results);
}
}
next();
}
function async(arg, callback) {
var delay = Math.floor(Math.random() * 5 + 1) * 100;
console.log('async with \''+arg+'\', return in '+delay+' ms');
setTimeout(function() {
callback(arg*2);
}, delay);
}
function final(results) {
console.log('Done', results);
}
series([
function(next) { async(1, next); },
function(next) { async(2, next); },
function(next) { async(3, next); },
function(next) { async(4, next); },
function(next) { async(5, next); },
function(next) { async(6, next); }
], final);
First, know that in JavaScript, functions can be passed as parameters to other functions. This is very different than passing the value returned from another function. Take this simple example:
function A() {
alert("In A");
}
function B(fn) {
alert("In B");
fn();
}
B(A); // alerts "In B", then "In A".
B(A()); // alerts "In A", then "In B",
// (then errors because the return value from A is undefined,
// and thus cannot be invoked.)
So, to follow your code example from beginning to end, here's how it goes...
series is a function that takes an array of functions (callbacks) and one more function (last) as parameters. It gets invoked first.
Within series, a function named next is defined (not to be confused with the parameter to each of the callback functions named next). The function next is invoked.
Within next, a variable named callback is defined. It's value is one of functions from the callbacks array in turn. The function stored in the callback variable gets invoked with an anonymous function.
Within callback, the async function is invoked. The same anonymous function from step 3 is passed to async. It's now called next, but this has nothing much to do with the next function defined in series.
Within async, some calculations are done and eventually the anonymous function is invoked (via setTimeout). It's called callback within the async function.
Within the anonymous function, some values are pushed onto the results array, and next is invoked. This is the next function defined in series.
Repeat steps 3 through 6 until all the functions within callbacks have been invoked and then the function in the parameter last (final) is invoked.
Clear as mud, right?
The series function takes a lists of functions to execute. Each of this functions must take a single parameter that needs to be a function, a callback. series uses that callback to know that a function finished its async work.
Here's a step by step of what series does:
Take a list of functions and a single callback named last
Create a results array where we'll store the results of all those functions
Pick the first item in the list and remove from the list
If the item is not a function (the list is empty):
Call last with the results array
Stop. We're done
If the item is a function call it and pass it a new callback that lets us know when it's done
Once the function is done doing its async work it should call the callback with any number of parameters. Store those parameters in the results array
Go to 3
Basically, it's a recursive function that waits until a process is done between each step. The results will be that each function you pass it in its callbacks list will be called sequentially, each after the previous finished doing its work.
Don't be discouraged. Async code is hard even for seasoned programmers.
Related
I am new to javascript, I have gone through tutorials about callbacks, but I don't see any that answers this, both method below offers the same results, the only difference I see is callback allows dynamically passing in a callback function.
Are there other advantages, I am missing?
Thank you.
Callback
function logOne(callback) {
setTimeout(() => {
console.log("one");
callback();
}, 1000);
}
function logTwo() {
console.log("two");
}
logOne(logTwo); // one, two
No Callback
function logOne() {
setTimeout(() => {
console.log("one");
logTwo();
}, 1000);
}
function logTwo() {
console.log("two");
}
logOne(); // one, two
Your first example is more flexible: You can use any callback, not just logTwo.
Both have their uses, it depends on whether you need the flexibility or whether logOne should be specifically tied to logTwo.
Callback of function: If you want to do some operation on some event
Like show time on click of a button. Then you override onclick
function for that button. So whenever (independent on time) that
button is clicked, that application internal framework will call
onclick event and your onclick function will be called.
Normal function : Every function is normal function
Calling a function is when you actually do the function.
Passing a function is when function A needs function B in order to
work. So when you call function A, you pass it function B as an
argument. In this case you are not calling function B, instead you are
providing it to function A so that function A can call it.
Your second example create a tight coupling between logOne and logTwo functions. This way you end up with a logOne function that can't be reused since it only works with one exact function.
By passing a callback, you make your function more flexible and generalized. Now it is able to work with a wide range of other functions as long as they have the same "shape" - same number of arguments in a same order.
I've got some experience in PHP, but I'm starting out with javascript and jquery. I'm working on my first project. I thought that scripting is scripting, and there will be little difference between this and PHP. Well was I wrong. For the first time I saw that something which is first in the code executes last!
Please have a look at this function which is meant to get svg and store them in json object to use as inline svg later
var svgIcons = { "arrow_left": "", "arrow_right":"", } //json object with empty values
this.getIcons = function() {
for (var icon_name in svgIcons) {
if (svgIcons.hasOwnProperty(icon_name)) {
var url=PHP.plugin_url+'/includes/icons/'+icon_name+'.svg';
jQuery.get(url, function(data) {
svgIcons[icon_name]=data;
console.log('iterating');
console.log(svgIcons[icon_name]); //outputs svg
});
}
}
console.log('this should be after iteration');
console.log(svgIcons["arrow_left"]); //empty
}
this.getIcons(); //called at object initialization
But the output is:
this should be after iteration
iterating
#document (and svg inside it)
iterating
#document (and svg inside it)
What is the cause of this change of order? Is it the get() function? How do I avoid situations like this?
jQuery.get is asynchronous. You are iterating inside the callback for an AJAX call, so that gets executed whenever the AJAX call is completed.
AJAX callbacks, setTimeout and setInterval are some asynchronous Javascript functions. Some threads you might find useful:
How does Asynchronous Javascript Execution happen?
Are all javascript callbacks asynchronous? If not, how do I know which are?
Edit: Yes, the function call ends before any of the callback stuff happens. Basically the execution of your JS will be linear, placing functions on a call stack whenever they are called. On the call-stack they are executed one-by-one, line-by-line. However, when one of those lines calls an asynchronous function (like a setTimeout or AJAX), the current execution places the async function on the call-stack and immediately returns to complete itself. So something like:
function myFunc(){
console.log('a');
setTimeout(function(){
console.log('b');
},0)
console.log('c');
}
myFunc();
would always log:
a
c
b
...even though the setTimeout is 0.
So, in your case what must be happening is that you are assigning the AJAX-received data to svgIcons[icon_name] inside the async callback (obviously), while the rest of your code which uses the object svgIcons is in the sequential/normal execution. You either have to move the code that uses the object inside the async callback, or use promises (basically promises are functions that are executed after an async call is completed).
2nd Edit: So, the reason you are not able to set svgIcons[icon_name] inside the callback is related to the things I was mentioning in my comment. When synchronous functions are called, they are placed on top of the current stack and executed right away, before returning to the calling function. So if you called a sync function inside a loop:
function outer(){
function inner(){
console.log(i);
}
for(var i=0;i<3;i++)
inner();
}
outer();
the synchronous inner function would be executed right away inside each loop, and would have access to the current value of i, so it would output 0, 1, 2 (as expected).
If however, inner was asynchronous, e.g
function outer(){
for (var i=0;i<3;i++)
setTimeout(function(){console.log(i)},0);
}
Then you would get 3, 3, 3 as the output!
This is because the loop has already finished, including the final i++.
So now I think you can see the problem with your code. Upto calling jQuery.get you have access to the current value of icon_name, but once we are inside that asynchronous callback, the current value disappears and is replaced by the last value for it, because the loop already completed before any of the callbacks were executed.
Try something like this:
var svgIcons = {}
var props = ["arrow_left","arrow_right"];
this.getIcons = function() {
props.forEach(function(prop){
var url=PHP.plugin_url+'/includes/icons/'+prop+'.svg';
jQuery.get(url, function(data) {
svgIcons[prop]=data;
var fullyLoaded = false;
for(var i=0;i<props.length;i++) {
if(!svgIcons.hasOwnProperty(props[i])){
fullyLoaded = false;
break;
}
else fullyLoaded = true;
} // end for loop
if(fullyLoaded)
callMyFunctionWhereIUseSvgIconsData();
}); //end jQuery.get()
});//end forEach
}
this.getIcons()
This uses the forEach method, which is native to arrays (MDN reference). Inside the function passed to forEach, the first argument is always the current element of the array (which I named as prop). So there is no messy loop or i, and every executing function has access to its own prop property.
Then, inside the AJAX callback, I assign the current prop to the data received, and then loop through all the properties to check if the svgIcons object has received the properties. So fullyLoaded will only evaluate to true once all the callbacks have been executed and the global svgIcons has received all the properties and data. Hence, you can now call the function that uses the object.
Hope this helps, feel free to ask further or let me know if the console throws errors.
Any ajax calls are async therefore it can be run while the ajax call is taking place. If you want to call something after all calls are done then try this:
var svgIcons = { "arrow_left": "", "arrow_right":"", } //json object with empty values
var executing = 0;
this.getIcons = function() {
for (var icon_name in svgIcons) {
//store that this call has started
exectuing = executing + 1;
if (svgIcons.hasOwnProperty(icon_name)) {
var url=PHP.plugin_url+'/includes/icons/'+icon_name+'.svg';
console.log('this will run as you were expecting');
//this ajax call is then started and moves to next iteration
jQuery.get(url, function(data) {
//This is run after the ajax call has returned a response, not in the order of the code
svgIcons[icon_name]=data;
console.log('iterating');
console.log(svgIcons[icon_name]); //outputs svg
//if you want to call a function after evey call is comeplete then ignore the 'executing' part and just call the function here.
//decrement value as this call has finished
executing = executing - 1;
//if all have finished then call the function we want
if(executing === 0){
executeAfter();
}
});
}
}
console.log('this should be after iteration');
console.log(svgIcons["arrow_left"]); //empty
}
this.executeAfter(){
//This will be exectued after all of you ajax calls are complete.
}
this.getIcons(); //called at object initialization
I'm looking to have better flow control for this async waterfall function.
async.waterfall([
async.apply(osxAppIconName, options.appFile),
function(iconFileName, callback) {
var existingIcon = path.join(options.iconDirectory, iconFileName);
return callback(null, existingIcon);
},
async.apply(fs.copy, options.iconFile), //automatically puts in existingIcon
async.apply(osxAppIconTouch, options.appFile),
], callback);
Right now I'm using async.apply which will inject global function arguments. In this case above I'm good, it takes existingIcon and will add it as the second-to-last argument in fs.copy. Making fs.copy's arguments options.iconfile, exitingIcon, callback [function]. Which is great!
However, let's say I need exitingIcon later on. Let's say I push this into my waterfall functions array.
async.apply(newFunction, existingIcon)
How would I get existingIcon to this function? Globals? It seems like a headache to manage! I also think that the async.apply function is executing on load, so if I pass it a variable it's gonna use the value of the variable when async.apply executes.
Thoughts? I have a proposal for async.switchboard here. Which attempts to solve this, but it doesn't work.
There's a nifty way around this using async.auto which attaches the result of a function to a prop in the chain.
return async.auto({
"icon_name": async.apply(osxAppIconName, options.appFile),
"copy_icon": ["icon_name",
function(callback, results) {
results.existing_icon = path.join(options.iconDirectory, results.icon_name);
return fs.copy(options.iconFile, results.existing_icon, callback);
}
],
"touch_icon": ["copy_icon", async.apply(osxAppIconTouch, options.appFile)],
}, callback);
For promises check out:
https://github.com/reggi/promise-ripple
https://www.npmjs.com/package/promise-auto
I've been trying to understand how Mike Bostock's queue.js works, but I can't see how it manages to work. The part I don't understand is how the code manages to continue executing callbacks. In particular, I am unsure about the pop() method (line 45). From my understanding, the method takes the next unprocessed, deferred function; appends a callback that (potentially) starts the next deferred function in the queue and executes when the immediately popped function finishes; then finally executes said function. My question is: what code executes this callback?
Each deferred function does not actually return anything -- they are expected to execute their final argument as a callback. For example, this will not work
var foo = function(i) {
console.log(i);
return i;
}
var finished = function(error, results) {
console.log(results);
}
queue(2)
.defer(foo, 1)
.defer(foo, 2)
.defer(foo, 3)
.defer(foo, 4)
.awaitAll(finished); // only prints "1" and "2", since foo() doesn't execute callbacks
However, if we modify foo to take a callback,
var foo = function(i, callback) {
console.log(i);
callback(null, i); // first argument is error reason, second is result
}
Then it will, as executing the callback causes queue to continue.
If I understand the code correctly, queue.await() and queue.awaitall() put the callback in the await instance variable, and then this is executed by notify().
I have a simple function to count the number of rows in a database and return the result. However I am getting the result returned as undefined, even though if I console.log the result it is coming up correctly from within the function. Here is the gist of what I am doing although I have stripped out a lot of code to simplify it.
$('#roomsList').append(getCount(currentRow.roomtype));
function getCount(roomtype) {
var query = "SELECT COUNT(*) FROM fixturesfittings WHERE roomtype = ?;"
localDatabase.transaction(function (trxn) {
trxn.executeSql(query, [propertyid,roomtype],function (transaction, results) {
return results.rows.item(0)["COUNT(*)"];
},errorHandler);
});
}
Can anyone help me?
The problem is localDataBase.transaction and exequteSql are asynchronous functions. They won't have their answer right away, which is why you pass the functions into them. Once they gets an answer, they calls your function, known as a callback. This will happen at a later point in the execution cycle, after getCount is long gone.
So getCount calls localDatabase.transaction, which gets to work but doesn't have anything immediately available, so getCount finishes before the data is available, and so getCount is returning undefined (the default return value in JavaScript functions).
You will probably need to rework your code to something like this:
getCount(function(count) {
$('#roomsList').append(count);
});
function getCount(callback) {
var query = '...';
localDatabase.transaction(function(trxn) {
trxn.exequteSql(query, ... function(transaction, results) {
callback(results);
});
}
}
This is a very common pattern JavaScript, and has lots of pitfalls and oddities to it. It takes some getting used to.
There are two equally serious problems with what you're doing:
Your outer function doesn't return anything. The only return statement in your code is returning from the inner function being passed to localDatabase.transaction.
Something like this:
function myFunction
inner_function = function () {
return 3; # Return from inner function, *not* myFunction
}
}
Even if you were attempting to communicate a value out of your inner function to the outer function and then returning from the outer function, it still wouldn't work; the inner function is being invoked asynchronously. That inner return results... is being invoked at some point in the future, long after getCount has finished executing and returned undefined. You need to pass in some form of callback into getCount; the callback will be invoked later and passed the count. Think of it this way:
function myFunc(callback) {
function inner_function = function(callback) {
// Invoke our callback sometime in the future
setTimeout(1000, callback);
}
// Inner function does some processing at some future time;
// give it a callback to invoke when its processing is done
inner_function(callback);
}