I was facing a problem with callbacks in Javascript. I solved my problem using what I would call an ugly property of Javascript (so to say, something that would logically be forbiden and never work in other languages than Javascript). So my question: Is there an ELEGANT way, to do the same thing.
I will so begin with the beginning. My goal was to wrap, in some manner, the Web Audio API. In the architecture, I implemented a class, lets call it AudioRessource, which is destined to be an interface (abstraction) in some manner of the AudioBuffer object of the Web Audio API.
This class (AudioRessource) have a prototype member function that must simply take an url as argument to automatically load audio data, decode it, handle errors, etc and finally hold the resulting AudioBuffer object in a "pseudo-private" member:
function AudioRessource()
{
this._aBuffer = null; // future reference to `AudioBuffer` object
this._loadStatus = 2;
};
AudioRessource.prototype.loadData = function(url) {
/* deal here with async functions to
provides audio data loading automation */
}
The main problem here, is that this will be an object instance (of AudioRessource) which will create the callback functions, using only local references, and must be able to pass the final AudioBuffer object to itself.
To load the raw audio data, this is pretty simple, I use the XMLHttpRequest object, with an extra property set as member of the XMLHttpRequest object, like this:
AudioRessource.prototype.loadData = function(url) {
let req = new XMLHttpRequest();
req.extraProperty = this; // reference to `AudioRessource` instance
req.onload = function(){
// retrive instance reference within the callback
this.extraProperty._loadStatus = 0;
}
req.onerror = function(){
// retrive instance reference within the callback
this.extraProperty._loadStatus = -1;
}
req.open('GET', url, true);
req.send(null);
this._loadStatus = 1;
}
The big problem appear when we have to decode the coded raw audio data into PCM data, that is, an Web Audio API AudioBuffer object instance. Indeed, the Web Audio API provides only one function to achieve this, and this function is asynchronous, and takes a callback that simply recieve the resulting buffer as argument: how to "catch" this resulting buffer to assign it to the proper AudioRessource instance (the one who lauched the process) ? This work that way:
AudioCtx.decodeAudioData(rawData,
function(result){
// do something with result },
function(error){
// do something with error });
My first naive approach, was to think like we were in C/C++ : I simply put an AudioRessource instance function "pointer" (reference) as callback, this way, the AudioRessource instance will directly recieve the buffer:
// where 'this' is an `AudioRessource` instance
AudioCtx.decodeAudioData(rawData,
this._handleDecodeSuccess,
this._handleDecodeError);
However, this does not work, because in javascript, this is not a "function pointer" that is passed into the decodeAudioData, but if I well undstand, an literal expression, that is, the "ASCII content" of the function... So the 'this' reference is lost !
I spent some time to try understand how this kind of asynchronous function is attended to work, since to me, coming from C/C++, this is simply an heresy: The function does not take any extra argument, no way to pass any external reference... "What is that thing ?". Then I finaly decided to try the "Illogical Javascript logic" way... And I found the solution :
// Create local variable which stores reference to 'this'
let thisInstReference = this;
// Use the local variable to write our callback
AudioCtx.decodeAudioData(rawData,
function(resut){
thisInstReference._aBuffer = result;
thisInstReference._loadStatus = 0;
},
function(resut){
thisInstReference._loadStatus = -3;
});
To be honnest, to me, this is simply freaking. First of all, I even don't understand what realy happen: HOW a local variable (to a object instance's member function), that stores a reference to an object instance (this), can be used "as this" in a callback function ? I do not even understand how a language can allow this kind of thing. Secondly, to me, this not a "proper way" to code something: this code is simply illogical, dirty, this works but this appear as an ugly hack that takes advantage of Javascript misdesign.
So here is my question: How to achieve this, in a elegant way ?
Your problem is simply due the the nature of how this works in javascript. The value of this is not bound at compile time nor at runtime but instead very late at call time.
In the following code:
AudioCtx.decodeAudioData(rawData,
this._handleDecodeSuccess,
this._handleDecodeError);
.. the value of this inside _handleDecodeSuccess and _handleDecodeError is not determined at object creation time but instead at the time they are called. And it is the decodeAudioData method that will eventually call them when decoding is complete. This causes the value of this to become something else (depending on how the functions are called).
The modern solution is to statically bind this to the functions:
AudioCtx.decodeAudioData(rawData,
this._handleDecodeSuccess.bind(this),
this._handleDecodeError.bind(this));
Note: the .bind() method creates a new function that wraps your function with this permanently bound to the argument you pass to it.
The traditional solution is to capture this inside a closure like what you have done.
Related
Coming from a C++ background, trying to work with an OO language that doesn't have explicit typing is a little more than a headache.
So I have dynamic elements for a webpage that are "controlled" by objects since there are tons of stuff I need to manage on each for it to work. The element is just the visual output of the data inside of the object itself, that's all I really need it for.
Except that I need the object to perform an internal function when it's clicked. That seems to be the biggest source of my headache thus far.
Javascript:
function onClick(file) //The external onClick function I use to try to get it to call from.
{
file.state = INUSE;
file.checkState();
}
function fileObject () { //The file object itself
this.element;
this.newElement();
//initialize stuff for the object
}
fileObject.prototype.newElement = function() { //creates a new element and sets its event listener
this.element.click(function() {onClick(this)});
}
fileObject.prototype.checkState = function() {/*does stuff*/} //apparently this is "not a function"
The error I get exactly is "file.checkState is not a function" from Firefox's console panel.
I'm still new to javascript, but after doing some debugging, I've come to find out that it's explicitly the onClick(this) function that is causing all of the errors. When used with something else, the onClick function works perfectly, but for some reason, the this keyword doesn't appear to actually be sending the reference to the fileObject since all checks show file being undefined when inside of the onClick scope.
Is there something fundamentally wrong about the way I'm trying to do this or am I just missing a step (or adding something that I don't need) that will help get this snippet working.
So you know, your initial problem isn't actually handling the action, but listening to it. click will trigger a synthetic click event, rather than liste for one.
You want ... .element.addEventListener("click", callback); that said, you face a second problem, immediately thereafter.
I will leave my example code as you've written it to not confuse the matter...
But when you see click( ) know that I mean subscribing with addEventListener, if element really does mean a browser DOM element. If it's not a standard browser element, and your own API, then ignore the previous portion, and carry on.
this is dynamically bound at the invocation time of the function (not at definition time).
The nearest function, scoped above, is your callback function that you are passing into .click( ... ).
Which is entirely different than the this which you mean outside of the callback.
Whatever is on the left-hand side of the dot is the this context for the duration of that particular invocation.
Needless to say, click() doesn't know enough to bind the this you mean, to the left-hand side of your callback.
The solution (or one of many) is to use lexical scoping and/or closure to retain the value of the object you mean.
// easy but messier
var fileObject = this;
... .click(function () { onClick(fileObject); });
// Cleaner with thunks:
function clickHandler (onClick, obj) {
return function () { onClick(obj); };
}
... .click(clickHandler(this));
Coming from c++ the way Javascript handles this will seem a little crazy, it looks like here you need to tell the function you've defined what this is - like so:
this.element.click(function() {onClick(this)}.bind(this));
Let's say I'm creating a chat system in javascript.
var ChatController = function() {
this.receiveMessageInteractor = new ReceiveMessageInteractor(this);
// ReceiveMessageInteractor delegate
this.didReceiveMessage = function(message) {
// ...
};
};
The ChatController also does some other stuff related to creating the html for the messages, but that's not important here.
The ChatController sets himself as a delegate of the ReceiveMessageInteractor, which will call the didReceiveMessage when a new message arrives.
var ReceiveMessageInteractor = function(delegate) {
this.observer = NotificationCenter.addObserver('DidReceiveMessageNotification' , function(data) {
var message = data['message'];
// format some message data
delegate.didReceiveMessage(message)
});
};
The ReceiveMessageInteractor just subscribes to a notification (NotificationCenter here is similar to the iOS one), does some formatting with the data and passes a message object to the delegate;
When the chat view goes of the screen (html gets deleted), my MenuController stops holding a pointer to ChatController, in which case I'd like it to be deleted, along with ReceiveMessageInteractor and observer.
The problem is that Javascript has no weak references, so ReceiveMessageInteractor is holding a pointer to ChatController, and even if ChatController wasn't holding a pointer to ReceiveMessageInteractor, my ChatController would still be alive, because the notification callback is holding a pointer to it (delegate).
So even if ReceiveMessageInteractor stopped existing, my ChatController would still not go away when the MenuController stops holding a pointer to it (because I can't have a weak reference inside the notification callback).
How do I solve this problem?
How do I solve this problem?
By understanding JavaScript. The problem is not that "Javascript has no weak references", the problem is that you don't know how to work without them because you come from a language that has them.
How would you remove that reference in any other language that doesn't have weak refs natively? Let's say C++. You would do like everyone does, including the implementors of the compiler/garbage collector/weak refs you're used to: you clean up after yourself.
function ChatController() {
this.receiveMessageInteractor = new ReceiveMessageInteractor(this);
// ReceiveMessageInteractor delegate
this.didReceiveMessage = function didReceiveMessage(message) {
// ...
};
this.destroy = function destroy() {
this.receiveMessageInteractor.destroy();
};
};
function ReceiveMessageInteractor(delegate) {
function callback(data) {
var message = data.message;
// format some message data
delegate.didReceiveMessage(message);
}
this.observer = NotificationCenter.addObserver('DidReceiveMessageNotification', callback);
this.destroy = function destroy() {
// Or however you NotificationCenter works, I don't know
NotificationCenter.removeObserver('DidReceiveMessageNotification', callback);
};
};
The Observer pattern implies resource management, even though it's not obvious (how is an "observation" relationship a resource??). Acquire and release. No hand-holding.
Also, notice the change in style. And please, learn the language, use prototypes, and, although not everyone will agree with me on this point, do not assign methods in the constructor.
edit: I forgot to add: ReceiveMessageInteractor? Really? What's wrong with MessageReceiver or something in that vein?
Your problem is not with the absence of weak references. All of your objects continue to have a hard reference, all originating from your NotificationCenter.
The NotificationCenter has a reference to the data handler, which has closure access to it's parent ReceiveMessageInteractor instance, as well as access to the delegate variable. Removing a reference to delegate from elsewhere won't break the anonymous function's access to it, therefore it stays.
What you'll need to do is add a .cleanup() method to each Controller that is called when it is removed.
In the ChatController.cleanup() method, you would want to call a method to remove the observer, something along the lines of this.receiveMessageInteractor.observer.unsubscribe().
The .unsubscribe() method should be defined in the NotificationCenter and remove the function(data) { ... } method you defined in .addObserver() from whatever data structure is holding it (or further down the line).
This is the same kind of pattern Facebook utilized in it's React framework + Flux architecture. Each component has a componentWillUnmount() method that assists in cleaning up data event handlers just like yours.
I am new to JSONP and had implemented cross domain functionality for my application and everything is working fine. Now i want to change my javascript code to apply object orientation.
My api is
http://localhost:8080/myApplication/getComments?callback=displayComments
CrossDomain.prototype.displayComments = function(data) {
// code to display the comments
}
Now I am getting an error in firebug given below
ReferenceError: displayComments is not defined
I changed the api to
http://localhost:8080/myApplication/getComments?callback=this.displayComments
and found that the function is appended inline to the callback like this
http://localhost:8080/myApplication/getComments?callback=callback=function (jsonData)
{
//code to display the comments
}
this time another error in firebug
SyntaxError: function statement requires a name
I have a doubt whether to use JSONP in object oriented javascript or not.
Please help.
Thanks in advance.
There's no point in defining the function on the prototype of a function unless you are going to create instances of that function, so start by doing that.
var myCrossDomain = new CrossDomain();
Then you have to call the method on the object, not as a global (it isn't a global, so you can't do that anyway)
var uri = "http://localhost:8080/myApplication/getComments?callback=" +
encodeURIComponent("myCrossDomain.displayComments");
In response to edits and comments:
Yes i am creating an instance of this in another js file
Then reference it as shown above.
I changed the api to
http://localhost:8080/myApplication/getComments?callback=this.displayComments
It's JSON-P. It runs by adding a new script element. Everything gets called in the global context. That is going to call this.displayComments which will be the same as window.displayComments.
If you want to call your method directly, then you need to specify the global variable holding the instance explicitly.
If you don't want to call it directly then you can use the more traditional approach of generating a new, anonymous function which has access to said object through a closure, assigning that function to a global variable (with a unique name) and using that name as your callback argument.
and found that the function is appended inline to the callback like this
http://localhost:8080/myApplication/getComments?callback=callback=function (jsonData)
You haven't shown us the code that creates the URI so we can't tell why that might be the case.
I am taking jQuery.Atmosphere.js as an example, in this it has public function such as onMessage, onError etc. And when implementing this api i have done the following
var socket = $.atmosphere;
var request = new $.atmosphere.AtmosphereRequest();
request.onMessage = function(response) {
// do what i want to do
}
Here the onMessage will be trigger whenever the server pushes data to browser. I don't understand how request.onMessage(response) get notified which is outside the atmosphere api? I have looked in to the jQuery.Atmosphere.js and couldn't connect the dots how this works. I am not talking about websocket or server push or anything about atmosphere framework. I just want understand how javascript function callbacks work. Can anyone point me an example how function callbacks work or send me a link so i can dig in?
Your syntax is incorrect, it should be:
request.onMessage = function(response) {
// do what I want to do
};
As you can see, the onMessage property must be set to a function. When the Message event occurs on this object, the function will be called. The jQuery.Atmosphere.js code contains:
f.onMessage(response);
where f is its internal variable representing the AtmosphereRequest object. This function is called from invokeFunction():
function _invokeFunction(response) {
_f(response, _request);
// Global
_f(response, jQuery.atmosphere);
}
_request is a local variable in the AtmosphereRequest constructor, which contains all the state of this request object. This is part of Javascript object oriented programming; all uses of this AtmosphereRequest object have access to these internal state variables.
The situation was that I wanted to create an instance of a helper class, but that helper class required initialisation through external scripts, so it was inherently asynchronous. With
var obj = new myObj();
clearly an call to
obj.myMethod();
would yield undefined, as obj would either be empty or undefined until its methods and params were loaded by the external script.
Yes, one could restructure things to have a callback pattern and work with the new object within that, but it gets cumbersome and awkward when working with a large and varied API with many dynamic objects as I've been working with.
My question has been, is there any possible way to cleverly get around this?
I imagine the academically trained programmers out there have a name for this sort of approach, but I put it here in case it's not better written somewhere.
What I've done is modify my loader class to use a placeholder+queue system to instantly return workable objects.
Here are the components. Sorry that there are jQuery bits mixed in, you can easily make this a pure-JS script but I've got it loaded anyway and I'm lazy.
'Client' makes this request, where 'caller' is my handler class:
var obj = caller.use('myObj',args);
In Caller, we have
Caller.prototype.use = function(objname,args) {
var _this = this;
var methods = ['method1','method2'];
var id = someRandomString();
this.myASyncLoader(objname,function(){
var q = [];
if (_this.objs[id].loadqueue) {
q = _this.objs[id].loadqueue;
}
_this.objs[id] = new myRemotelyLoadedClass(args);
//realise all our placeholder stuff is now gone, we kept the queue in 'q'
_this.objs[id].isloaded = true;
//once again, the jquery is unnecessary, sorry
$.each(q,function(a,b){
_this.objs[id][b['f']](b['a']);
});
});
_this.objs[id] = _this.createPlaceholderObj(methods,id);
return _this.objs[id];
}
This function basically initiates the loader function, and when that's done loads a new instance of the desired class. But in the meantime it immediately returns something, a placeholder object that we're going to load with all of our remotely loaded object's methods. In this example we have to explicitly declare them in an array which is a bit cumbersome but liveable, though I'm sure you can think of a better way to do it for your own purposes.
You see we're keeping both the temporary object and future object in a class-global array 'objs', associated with a random key.
Here's the createPlaceholderObj method:
Caller.prototype.createPlaceholderObj = function(methods,id) {
var _this = this;
var n = {};
n.tempid = id;
n.isloaded = false;
$.each(methods,function(a,methodCalled){
n[methodCalled] = function(){
_this.queueCall(id,methodCalled,arguments);
}
});
return n;
}
Here we're just loading up the new obj with the required methods, also storing the ID, which is important. We assign to the new methods a third function, queueCall, to which we pass the method called and any arguments it was sent with. Here's that method:
Caller.prototype.queueCall = function(id,methodName,args) {
if (this.objs[id].isloaded == true) {
this.objs[id][methodName](args);
} else {
if (this.objs[id].loadqueue) {
this.objs[id].loadqueue.push({'f':methodName,'a':args});
} else {
var arr = [{'f':methodName,'a':args}];
this.objs[id].loadqueue = arr;
}
}
}
This method will be called each time the client script is calling a method of our new object instance, whether its logic has actually been loaded or not. The IF statement here checks which is the case (isloaded is set to true in the caller method as soon as the async function is done). If the object is not loaded, the methodName and arguments are added to a queue array as a property of our placeholder. If it is loaded, then we can simply execute the method.
Back in the caller method, that last unexplained bit is where we check to see if there is a queue, and if there is, loop through it and execute the stored method names and arguments.
And that's it! Now I can do:
var obj = caller.use('myObj',args);
obj.someMethod('cool');
obj.anotherMethod('beans');
and while there might be a slight delay before those methods actually get executed, they'll run without complaint!
Not too short a solution, but if you're working on a big project you can just put this in one place and it will pay many dividends.
I'm hoping for some follow-ups to this question. I wonder, for example, how some of you would do this using a deferred-promise pattern? Or if there are any other ways? Or if anyone knows what this technique is called? Input from JS whizzes much appreciated.