In javascript it's very popular for libraries/frameworks to let us define a callback function for post-processing of data.
eg.
load("5", function(element) {
alert(element.name);
});
I wonder how the load() function looks like to be able to let the user provide a callback?
Are there good tutorials for this?
Well, the load function could look like this:
function load(arg, callback) {
var element = { name: "foo " + arg }; // something to pass
if (typeof callback == 'function') {
callback(element);
}
}
With the typeof check we make sure that the callback argument is an object that we can invoke, a function.
Then your example:
load("5", function(element) {
alert(element.name); // Will show `"foo 5"`.
});
In JavasScript functions are first-class objects. That pretty much means they act like other built in types. You can assign them to variables, pass them into functions, etc.
This article is a helpful link explaining how functions as first-class objects work in JavaScript:
http://helephant.com/2008/08/functions-are-first-class-objects-in-javascript/
Joel Spolsky has a detailed and interesting explanation on some of the interesting things/ways you can use functions as first class objects in JavaScript: http://www.joelonsoftware.com/items/2006/08/01.html
Finally, since they're first class objects, functions can very easily accept other functions as parameters:
var load = function(callback) {
// load something here
callback();
}
function load(foo, callback) {
blah(foo);
callback();
}
function load( number, callback){
// do something
if (callback) {
callback();
}
}
Functions are just like normal types and can be passed as arguments:
function load(param1, callback) {
// call the callback parameter one second later
setTimeout(function() {
callback(param1);
}, 1000);
}
function(callback,argumentArray) {
var result = callback.apply(this,argumentArray);
}
Points to be noted:
this can be null. In that case if this is used in the callback implementation then it would point to the Global object.
argumentArray is actually a JavaScript Array of arguments required by the callback method.
Related
So basically why do I have to use this kind of method in these kind of situations in particular?
function Example(callback) {
// What's the purpose of both 'call()' and 'null'?
callback.call(null, "Hello")
}
Exemple(function(callback) {
alert();
})
I've figured it out this syntax in a open project code but I haven't found out why it works yet.
You don't need to use call() in this situation. call() is used when you need to set the value of this in a function. If you aren't doing that, just call the function like you would any other function. For example:
function Example(callback) {
// call the function passed in
callback("Hello")
// this also works, but there's no benefit here
// because the callback doesn't care about `this`
// callback.call(null, "Hello")
}
// pass a callback function to Example
Example(function(text) {
console.log(text);
})
You would use call in situations where the callback function needs a value for this. For example:
function Example(callback) {
let person = {name: "Mark"}
// set this in the callback to the person object
// and the execute it with the argument "Howdy"
callback.call(person, "Howdy")
}
Example(function(greeting) {
// the callback function depends on having this.name defined
console.log(greeting, this.name);
})
This looks pointless at the first glance, however, there might be valid reasons for this syntax, for example:
the callback relies explicitly on this being null and not some global default
the callback is not necessarily a function, it can be a "function-alike" object that provides a .call method
callback.call(... is used consistently through the application, no matter if this is needed or not
Say I have the following code.
function myFunc(item) {
console.log(item);
}
function action() {
return myFunc("Hello World");
}
const myUncalledFunction = action;
myUncalledFunction(); // will console.log "Hello World"
Is there a way to make this code cleaner? I've looked into .apply() but it looks like that calls the function immediately as well.
The reason I'm wanting to do this is Chai's expect for throw requires you to pass in an uncalled function. But the function I'm wanting to test requires arguments to be passed in. So currently I just created a wrapper function that returns the function with the arguments passed in.
I feel like there has to be a better way to achieve this that is cleaner and simpler for someone looking at it.
Use .bind to bind arguments (and a calling context, if needed) to a function, which produces a new callable function in the process. Here, you can see this as basically allowing you to pass in arguments at one time, and then call the desired function with those arguments later:
function myFunc(item) {
console.log(item);
}
const myUncalledFunction = myFunc.bind(undefined, 'Hello World');
myUncalledFunction();
Or you might just use a concise arrow function
function myFunc(item) {
console.log(item);
}
const myUncalledFunction = () => myFunc('Hello World');
myUncalledFunction();
I have several async functions with varying numbers of parameters, in each the last param is a callback. I wish to call these in order. For instance.
function getData(url, callback){
}
function parseData(data, callback){
}
By using this:
Function.prototype.then = function(f){
var ff = this;
return function(){ ff.apply(null, [].slice.call(arguments).concat(f)) }
}
it is possible to call these functions like this, and have the output print to console.log.
getData.then(parseData.then(console.log.bind(console)))('/mydata.json');
I've been trying to use this syntax instead, and cannot get the Then function correct. Any ideas?
getData.then(parseData).then(console.log.bind(console))('/mydata.json');
Implementing a function or library that allows you to chain methods like above is a non-trivial task and requires substantial effort. The main problem with the example above is the constant context changing - it is very difficult to manage the state of the call chain without memory leaks (i.e. saving a reference to all chained functions into a module-level variable -> GC will never free the functions from memory).
If you are interested in this kind of programming strategy I highly encourage you to use an existing, established and well-tested library, like Promise or q. I personally recommend the former as it attempts to behave as close as possible to ECMAScript 6's Promise specification.
For educational purposes, I recommend you take a look at how the Promise library works internally - I am quite sure you will learn a lot by inspecting its source code and playing around with it.
Robert Rossmann is right. But I'm willing to answer purely for academic purposes.
Let's simplify your code to:
Function.prototype.then = function (callback){
var inner = this;
return function (arg) { return inner(arg, callback); }
}
and:
function getData(url, callback) {
...
}
Let's analyze the types of each function:
getData is (string, function(argument, ...)) → null.
function(argument, function).then is (function(argument, ...)) → function(argument).
That's the core of the problem. When you do:
getData.then(function (argument) {}) it actually returns a function with the type function(argument). That's why .then can't be called onto it, because .then expects to be called onto a function(argument, function) type.
What you want to do, is wrap the callback function. (In the case of getData.then(parseData).then(f), you want to wrap parseData with f, not the result of getData.then(parseData).
Here's my solution:
Function.prototype.setCallback = function (c) { this.callback = c; }
Function.prototype.getCallback = function () { return this.callback; }
Function.prototype.then = function (f) {
var ff = this;
var outer = function () {
var callback = outer.getCallback();
return ff.apply(null, [].slice.call(arguments).concat(callback));
};
if (this.getCallback() === undefined) {
outer.setCallback(f);
} else {
outer.setCallback(ff.getCallback().then(f));
}
return outer;
}
This looks like an excellent use for the Promise object. Promises improve reusability of callback functions by providing a common interface to asynchronous computation. Instead of having each function accept a callback parameter, Promises allow you to encapsulate the asynchronous part of your function in a Promise object. Then you can use the Promise methods (Promise.all, Promise.prototype.then) to chain your asynchronous operations together. Here's how your example translates:
// Instead of accepting both a url and a callback, you accept just a url. Rather than
// thinking about a Promise as a function that returns data, you can think of it as
// data that hasn't loaded or doesn't exist yet (i.e., promised data).
function getData(url) {
return new Promise(function (resolve, reject) {
// Use resolve as the callback parameter.
});
}
function parseData(data) {
// Does parseData really need to be asynchronous? If not leave out the
// Promise and write this function synchronously.
return new Promise(function (resolve, reject) {
});
}
getData("someurl").then(parseData).then(function (data) {
console.log(data);
});
// or with a synchronous parseData
getData("someurl").then(function (data) {
console.log(parseData(data));
});
Also, I should note that Promises currently don't have excellent browser support. Luckily you're covered since there are plenty of polyfills such as this one that provide much of the same functionality as native Promises.
Edit:
Alternatively, instead of changing the Function.prototype, how about implementing a chain method that takes as input a list of asynchronous functions and a seed value and pipes that seed value through each async function:
function chainAsync(seed, functions, callback) {
if (functions.length === 0) callback(seed);
functions[0](seed, function (value) {
chainAsync(value, functions.slice(1), callback);
});
}
chainAsync("someurl", [getData, parseData], function (data) {
console.log(data);
});
Edit Again:
The solutions presented above are far from robust, if you want a more extensive solution check out something like https://github.com/caolan/async.
I had some thoughts about that problem and created the following code which kinda meets your requirements. Still - I know that this concept is far away from perfect. The reasons are commented in the code and below.
Function.prototype._thenify = {
queue:[],
then:function(nextOne){
// Push the item to the queue
this._thenify.queue.push(nextOne);
return this;
},
handOver:function(){
// hand over the data to the next function, calling it in the same context (so we dont loose the queue)
this._thenify.queue.shift().apply(this, arguments);
return this;
}
}
Function.prototype.then = function(){ return this._thenify.then.apply(this, arguments) };
Function.prototype.handOver = function(){ return this._thenify.handOver.apply(this, arguments) };
function getData(json){
// simulate asyncronous call
setTimeout(function(){ getData.handOver(json, 'params from getData'); }, 10);
// we cant call this.handOver() because a new context is created for every function-call
// That means you have to do it like this or bind the context of from getData to the function itself
// which means every time the function is called you have the same context
}
function parseData(){
// simulate asyncronous call
setTimeout(function(){ parseData.handOver('params from parseData'); }, 10);
// Here we can use this.handOver cause parseData is called in the context of getData
// for clarity-reasons I let it like that
}
getData
.then(function(){ console.log(arguments); this.handOver(); }) // see how we can use this here
.then(parseData)
.then(console.log)('/mydata.json'); // Here we actually starting the chain with the call of the function
// To call the chain in the getData-context (so you can always do this.handOver()) do it like that:
// getData
// .then(function(){ console.log(arguments); this.handOver(); })
// .then(parseData)
// .then(console.log).bind(getData)('/mydata.json');
Problems and Facts:
the complete chain is executed in the context of the first function
you have to use the function itself to call handOver at least with the first Element of the chain
if you create a new chain using the function you already used, it will conflict when it runs to the same time
it is possible to use a function twice in the chain (e.g. getData)
because of the shared conext you can set a property in one function and read it in one of the following functions
At least for the first Problem you could solve it with not calling the next function in the chain in the same context and instead give the queue as parameter to the next function. I will try this approach later. This maybe would solve the conflicts mentioned at point 3, too.
For the other problem you could use the sample Code in the comments
PS: When you run the snipped make sure your console is open to see the output
PPS: Every comment on this approach is welcome!
The problem is that then returns a wrapper for the current function and successive chained calls will wrap it again, instead of wrapping the previous callback. One way to achieve that is to use closures and overwrite then on each call:
Function.prototype.then = function(f){
var ff = this;
function wrapCallback(previousCallback, callback) {
var wrapper = function(){
previousCallback.apply(null, [].slice.call(arguments).concat(callback));
};
ff.then = wrapper.then = function(f) {
callback = wrapCallback(callback, f); //a new chained call, so wrap the callback
return ff;
}
return wrapper;
}
return ff = wrapCallback(this, f); //"replace" the original function with the wrapper and return that
}
/*
* Example
*/
function getData(json, callback){
setTimeout( function() { callback(json) }, 100);
}
function parseData(data, callback){
callback(data, 'Hello');
}
function doSomething(data, text, callback) {
callback(text);
}
function printData(data) {
console.log(data); //should print 'Hello'
}
getData
.then(parseData)
.then(doSomething)
.then(printData)('/mydata.json');
I've been struggling for awhile with callbacks in general. I'm trying to go back to the basics to try to understand conceptually and this is what I understand so far (yeah, basic).
function RandHash (callback) {
ds_hash = Math.floor((Math.random()*10000)+1);
callback();
}
function CompiledHash (){
console.log(ds_hash);
}
var ds_hash;
RandHash(Compiled Hash);
Will yield the random number.
However, I'm lost as to how to get the "ds_hash" variable returned from the callback.
Seems this would work:
var ds_hash;
ds_hash = RandHash(Compiled Hash);
It doesn't. If I try to return the value something like:
function CompiledHash (){
return ds_hash;
}
It doesn't do anything.
Please help me with this. Seems I spend 90% of my time with node in callback debugging. I've built some decent applications but everything has been handled through the async library because of this mental block I have.
Thanks.
The first error you've made is that RandHash hasn't returned anything. There's no return statement present in the function, so var anything = RandHash(alsoAnything) will always result in anything being undefined.
Functions are first class variables
Functions can be used just as variables can, and passed round as arguments to functions. This is a powerful feature of javascript and something that you'll need to grok to use callbacks. Think of a function as a defined action, and you're just passing that action around.
Also, callbacks should be used to deal with the completion of a process. So the idea is, you know what is meant to happen after process A, and what it is meant to generate, so you can give process A a callback which will be called once A has terminated. Now the scenario here isn't appropriate for a callback situation. It would be much easier for you to do...
function RandHash () {
return Math.floor((Math.random()*10000)+1);
}
And then get your hash by just calling this function, like so...
var hash = RandHash();
You also want to be aware of javascripts variable scoping, as you're missing the var keyword when referencing ds_hash in the RandHash function which means the assignment defaults to global scope. This is probably what is responsible for confusing you, as your assignment of ds_hash in RandHash will be global and therefore available in CompiledHash, meaning some functions will still be able to access the ds_hash value despite this not being the correct, or proper way to do this.
Assuming you will eventually require asynchronous processing
function randHash (callback) {
var hash = /* do hash calculation */
callback(hash);
}
function compileHash (hash) {
/* do something using the hash variable passed to this function */
}
randHash(compileHash); // pass the compileHash function as the callback
You should pass your variables as arguments to the callback. That will deal with your scoping issues hopefully.
Also, small note, functions in javascript should typically be lowercase if they aren't going to be used with the new statement (ie, a javascript class).
To truly understand asynchronous behaviour, you should try it with something that is actually asynchronous, and passing values around, not just a global variable, as that will never work when you move on to asynchronous functions :
function RandHash (callback) {
setTimeout(function() {
var ds_hash = Math.floor((Math.random()*10000)+1);
callback(ds_hash); // execute callback when async operation is done
}, 300);
}
function CompiledHash(hash){ // pass hash
console.log(hash);
}
RandHash(function(returned_hash) { // this is the callback function
CompiledHash(returned_hash); // pass along the returned data
});
The callback function is executed once the asynchronous function, in this case setTimeout, has completed, and passes the data back as an argument.
The callback argument is just the function that is passes as an argument.
// regular function
function doStuff(argument) {
// where argument can be anything, also a function
}
// you can pass a string
doStuff('string');
// or a function
doStuff(function(argument_returned) {
});
// and passing a function you can execute that function with arguments in the original function
function doStuff(argument) {
var argument_returned = 'string';
argument(argument_returned); // executes the passsed function
});
Try this:
function RandHash (callback) {
ds_hash = Math.floor((Math.random()*10000)+1);
callback(ds_hash);
}
function CompiledHash(ds_hash){
console.log(ds_hash);
}
var ds_hash;
RandHash(CompiledHash);
For callbacks and functions, it's a bit difficult to understand variable scopes, and what you are doing is not really recommended. You can pass parameters into callbacks, and should get passed, but I would suggest building it like this:
function RandHash (callback) {
var ds_hash = Math.floor((Math.random()*10000)+1);
callback(ds_hash);
}
function CompiledHash(ds_hash){
console.log(ds_hash);
}
RandHash(CompiledHash);
A couple notes:
If you want to propagate the result of the callback then you need to return its output in RandHash
You don't need ds_hash to be a global variable if you're planning on returning it, anyway
The line RandHash(Compiled Hash); is a syntax error (notice the space in the variable name)
Try this:
function RandHash(callback) {
var ds_hash = Math.floor((Math.random() * 10000) + 1);
return callback(ds_hash);
}
function CompiledHash(ds_hash) {
console.log(ds_hash);
return ds_hash;
}
RandHash(CompiledHash);
Notice that there are no global variables. The callback returns a value and the function that executes the callback passes it along.
On a style note, you should only capitalize the names of functions that you intend to use as a constructor.
This will do
function RandHash (callback) {
var ds_hash = Math.floor((Math.random()*10000)+1);
callback(ds_hash);
}
var CompiledHash = function (ds_hash){
console.log(ds_hash);
}
RandHash(CompiledHash);
This is not a jquery question.
I want to create a callback function...so users would use it like so:
myfunc.init('id',{option1: val, option2: val2}, function() {
//in here users can now call methods of myfunc
});
How do I do this within my code. OS once I know my script is ready I want to be able to somehow call this anonymous function.
Hope this makes sense. I often don't.
You can write i t like this:
var myfunc.init(id, options, callbackFunction){
//do whatever you want with id & options
callbackFunction();
}
This will first run everything you want in your init function then run the function supplied as the callback parameter
Something like this:
var myfunc.init = function(id, options, func) {
// call func somewhere at some point
func();
};
Usually callbacks are used because you want to call it at some point at time which is unknown (asynch)... like for example when an AJAX request is complete:
var myfunc.init = function(id, options, func) {
myAjaxRequest("url.com", function() {
// call the func at this point in time
func();
});
};