Closures for nested anonymous methods in javascript - javascript

I am trying to set a variable value at an object (class) level from deep inside a few levels of nested anonymous javascript method calls. How do I do this?
Here is some code to explain what I am trying to do. Disclaimer: I am not that comfortable with the concept of closures in javascript, so I might be going along a wrong path here. Any suggestion about a succinct way to achieve what I want to do would be greatly appreciated.
// FileUtils object.
var FileUtils = function () {
// Member variables.
this.ConfRootDir = null;
};
// Method to get a file entry.
// successCallback has to be a method with a FileEntry object.
FileUtils.prototype.getFileEntry = function (fileName, successCallback) {
if (this.ConfRootDir == null) {
var thisObj = this;
// Request the root filesystem
// [** 1st callback, using anon method]
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0,
function (fileSystem) {
// Get the root directory of the file system.
var rootDir = fileSystem.root;
// Get the ConferenceToGo directory, or create if required.
// [** 2nd callback, using anon method]
rootDir.getDirectory("ConferenceToGo", { create: true, exclusive: false },
function (confDir) {
// Set values for future use
// [** Definitely wrong scoping. The class level variable
// can't be accessed using 'this'. What to do? **]
this.ConfRootDir = confDir;
// Now try getting the handle for the list file.
// [** 3rd callback, using anon method. Irrelevant at this point.]
this.ConfRootDir.getFile(fileName, { create: false },
successCallback, // Success callback [getFile]
function (error) {
logError("Unable to retrieve file: ", true, true, error);
}); // Failure callback [getFile]
}, // Success callback [getDirectory]
function (error) { logError("Unable to create new directory: ", true, true, error); }); // Failure callback [getDirectory]
}, // Success callback [requestFileSystem]
function (error) { logError("Problem reading file system: ", true, true, error); }
);
}
}
I know that the scoping (by using 'this') is all wrong in the above piece of code, but not sure how to get it right. I have seen a few answers about binding to context (like this one), but I am using anonymous methods so that makes it harder. Note: Although I show only one method in the prototype of FileUtils here, there a few more.
Those who are aware can probably recognize that I am using methods from the cordova (PhoneGap) library for cross-platform mobile dev in HTML5 and JS, but that is not really much relevant here.

… function() { function() { function() { …
// Set values for future use
// [** Definitely wrong scoping. The class level variable
// can't be accessed using 'this'. What to do? **]
this.ConfRootDir = confDir;
You already have preparated the answer: thisObj.ConfRootDir. The thisObj variable is available in the scope of the nested function, and still points to the this keyword of the outer getFileEntry function, i.e. to the FileUtils instance.

Related

Meteor - Meteor.Call inside helper not return value to helper [duplicate]

How can I define a Meteor method which is also callable in a template helper?
I have these two files:
file: lib/test.js
Meteor.methods({
viewTest : function (str) {
return str;
}
});
file: client/myView.js
Template.helloWorld.helpers({
txt : function () {
var str = Meteor.call('viewTest', 'Hello World.');
return str;
}
});
When I give "str" a normal string everything works fine. But in this case my template does not get any value. I defined - for the test - in the same file where the method is a normal function and tried to call the function. The error I got was that the function does not exist. So I think that Meteor tries to render the template before it knows anything about the methods I defined for it. But I think that this is a bit unusual - isn't it?
There is now a new way to do this (Meteor 0.9.3.1) which doesn't pollute the Session namespace
Template.helloWorld.helpers({
txt: function () {
return Template.instance().myAsyncValue.get();
}
});
Template.helloWorld.created = function (){
var self = this;
self.myAsyncValue = new ReactiveVar("Waiting for response from server...");
Meteor.call('getAsyncValue', function (err, asyncValue) {
if (err)
console.log(err);
else
self.myAsyncValue.set(asyncValue);
});
}
In the 'created' callback, you create a new instance of a ReactiveVariable (see docs) and attach it to the template instance.
You then call your method and when the callback fires, you attach the returned value to the reactive variable.
You can then set up your helper to return the value of the reactive variable (which is attached to the template instance now), and it will rerun when the method returns.
But note you'll have to add the reactive-var package for it to work
$ meteor add reactive-var
Sashko added a neat little package called meteor-reactive-method to solve this problem.
$ meteor add simple:reactive-method
Template.helloWorld.helpers({
txt: function() {
return ReactiveMethod.call('viewTest', 'Hello World.');
}
});
As I point out in common mistakes, helpers should be side-effect free, so I'd use this technique with caution. However, it's a really handy shortcut for cases where:
The helper should fire only once (it doesn't depend on reactive state).
The invoked method doesn't mutate the database.
You need to interface your return value with a Session variable as the request is asynchronous:
Template.helloWorld.helpers({
txt : function () {
return Session.get("txt") || "Loading";
}
});
Template.helloWorld.created = function() {
Meteor.call('viewTest', 'Hello World.', function(err, result) {
Session.set("txt", result);
});
}
So .rendered should be called once when your template loads (at least it should with the newer version of Meteor.)
The value would be called and displayed. Otherwise it would say "Loading".
Methods on the client side are asynchronous, and their return value is always undefined. To get the actual value returned by the method, you need to provide a callback:
Meteor.call('method', 'argument', function(error, result) {
....
});
Now, there's no easy way to use the result in your helper. However, you can store it in your template as a data object and then return it in the helper:
Template.template.created = function() {
var self = this;
self.data.elephantDep = new Deps.Dependency();
self.data.elephant = '';
Meteor.call('getElephant', 'greenOne', function(error, result) {
self.data.elephant = result;
self.data.elephantDep.changed();
});
};
Template.template.showElephant = function() {
this.elephantDep.depend();
return this.elephant;
};
This is expected behavior. You are not using methods as they are intended.
Your code defines a server method viewTest and a corresponding method stub on the client with the same name.
Meteor.call('viewTest', 'Hello World.'); remotely calls viewTest on the server and in parallel runs the stub on the client.
Regarding the return value of the stub please see the documentation here, in particular:
On the client, the return value of a stub is ignored. Stubs are run
for their side-effects: they are intended to simulate the result of
what the server's method will do, but without waiting for the round
trip delay.
Regarding the return value of the server method please see the documentation here, in particular:
On the client, if you do not pass a callback and you are not inside a
stub, call will return undefined, and you will have no way to get the
return value of the method. That is because the client doesn't have
fibers, so there is not actually any way it can block on the remote
execution of a method.
There is a fine little package for this by #msavin:
https://atmospherejs.com/msavin/fetcher

How to call a method outside a closure

I'm trying to use the NodeJS module "pcsc-lite" to communicate with a card reader. If you want to take a look at the module : https://github.com/santigimeno/node-pcsclite.
I'm looking for a way to send a sequence of data to my reader using my own method. Because, the module is event-based. So I have to declare two listeners (one in the other) to be able to call the send method.
For example :
module.on("reader", function(reader){
//...
reader.on("status", function(status){
//...
reader.connect({ share_mode : this.SCARD_SHARE_SHARED },function(err, protocol) {
//This is the method I want to be able to call "when I need it"
reader.transmit(...);
});
});
});
I would like to call the transmit method like this for example :
function send(...){
reader.transmit(...);
}
I think there is a way to do it, but I seem to be a little bit hooked to my C/Java programming habits.
Thanks in advance.
If your reader will be a singleton, you can declare it outside the callback, and then assign the variable when you're ready. Without knowing more, here's a simple example:
let reader; // we prepare a variable that's outside of scope of it all.
// your `send` function
function send(params) {
let stuff = doStuffWithParams(params);
reader.transmit(stuff, callback);
}
// we take out init stuff too
function initialize() {
// we know reader variable is already initialized.
reader.on('status', function() {
reader.connect({
share_mode : this.SCARD_SHARE_SHARED
},function(err, protocol) {
// send.
send();
// or even better, emit some event or call some callback from here, to let somebody outside this module know you're ready, then they can call your `send` method.
});
});
}
// now your module init section
let pcsc = require('pcsclite')();
pcsc.on('reader', function(r) {
// assign it to our global reader
reader = r;
initialize();
});
Note: don't call your variables module, it's refering to the file being currently executed and you can get unexpected behavior.

Dojo - Promise Can't Access Variables

I am working with dojo in the ESRI Web App Builder and have come across a situation where I need to run an AJAX call and still access a variable from the base class. Below is my code with comments in it to explain exactly where it is successful and exactly where it is failing:
define(['dojo/_base/declare', 'jimu/BaseWidget', 'dojo/request', "esri/layers/WMSLayer", "esri/config", "dojo/domReady!"], function (declare, BaseWidget, request, WMSLayer, esriConfig) {
return declare([BaseWidget], {
baseClass: 'jimu-widget-mywidget',
// This function is called by a button press (Normally the WMSLayer variable would be set by user input)
addWms: function () {
var wmsLayer = new WMSLayer("http://sampleserver1.arcgisonline.com/ArcGIS/services/Specialty/ESRI_StatesCitiesRivers_USA/MapServer/WMSServer", {
format: "png",
visibleLayers: [2]
});
this.map.addLayer(wmsLayer); // this.map is inherited from BaseWidget as far as I can see. This adds a wms to my map without error
request("request.html").then(function(data){
var wmsLayer = new WMSLayer("http://sampleserver1.arcgisonline.com/ArcGIS/services/Specialty/ESRI_StatesCitiesRivers_USA/MapServer/WMSServer", {
format: "png",
visibleLayers: [2]
});
this.map.addLayer(wmsLayer); // This is now in another context....I get the error HERE.
// At this point map is not defined because this anonymous function is running
// in a different context. ( at least I think that's what is happening )
}, function(err){
// Hopefully there are no typos in my example XD
});
}
});
});
My question is --> How do I access the "map" variable from withing the callback function of "request"?
I want to be able to run this.map.addLayers from my call to the GetCapabilities of a WMS service. The request would normally call it and I get all the way to the end of my code until I can't access the "map" variable anymore as I know it.
Dojo type answer is preferred but plain old javaScript is also fine. Please avoid JQuery answers.
Resources are:
ESRI JavaScript library
Dojo
ESRI Web App Builder
The problem you're running into is the classic problem of execution context being lost when an asynchronous callback is invoked (and thus this no longer means what you want). There are generally two ways to resolve this.
One way is to create a variable in the outer scope that references what this is, so that the inner function can access it:
var self = this;
request('request.html').then(function (data) {
// ...
self.map.addLayer(wmsLayer);
});
The other way is to use context binding, either via Function#bind (ES5) or dojo/_base/lang.hitch:
// Function#bind:
request('request.html').then(function (data) {
// ...
this.map.addLayer(wmsLayer);
}.bind(this));
// lang.hitch:
request('request.html').then(lang.hitch(this, function (data) {
// ...
this.map.addLayer(wmsLayer);
}));
It's also common with the binding approach to break out the asynchronous handler function to another internal instance method:
_addRequestedLayer: function () {
// ...
this.map.addLayer(wmsLayer);
},
addWms: function () {
// ...
// ES5:
request('request.html').then(this._addRequestedLayer.bind(this));
// ...or lang.hitch, with late binding:
request('request.html').then(lang.hitch(this, '_addRequestedLayer'));
There is also a tutorial on lang.hitch.
In your request callback, this does not refer to your class anymore. A simple way to have access to the map in the callback is assigning the reference of the map in a variable on addWms function scope and then using it in your callback:
addWms: function() {
var map = this.map;
// Your code here
request('request.html').then(function (data) {
// Your code here
map.addLayer(wmsLayer); // Note that you're using map instead of this.map
}
}
You can also use Dojo hitch function, where you can pass a function and the context that it should be applied. I suggest that you take a look at this link http://dojotoolkit.org/reference-guide/1.10/dojo/_base/lang.html#hitch.

Backbone trouble with calling functions

I'm very inexperienced with javascript so forgive me if this is really obvious...
I'm using the TodoMVC example found here (http://todomvc.com/architecture-examples/backbone/) as a foundation for a Cordova project. The trouble that I'm having is combining this with the Camera and Geolocation plugins, I get errors calling any functions in js/views/app-view.js from within the Camera/Geolocation callback functions. I'm assuming it's a problem of context but I don't know enough about javascript to fix it.
For example, the TodoMVC project adds new items using this line of code, in js\views\app-view.js:
app.todos.create(this.newAttributes());
I'm trying to combine this with the camera function to take a picture with a phone's camera, then add a new todo item with the picture stored in it. Everything works fine except when I try to have that above line of code in the camera callback function, at the bottom like this:
onCameraSuccess: function(imageData) {
// Stores image data in a hidden field to be used later. Not best method but it works
document.getElementById('imageData').value = "data:image/jpeg;base64," + imageData;
// This line should create a new item
app.todos.create(this.newAttributes());
},
It gives the error "Undefined is not a function", seemingly referring to "this.newAttributes()". Again it seems like this is a matter of context, because that very same line works almost anywhere else in the same script.
OnCameraSuccess is a callback function from the Camera's getPicture function:
Camera: function() {
navigator.camera.getPicture(this.onCameraSuccess, this.onCameraFail, { quality: 50,
destinationType: Camera.DestinationType.DATA_URL,
targetWidth: 250,
targetHeight: 250,
correctOrientation: true
});
},
Likewise, a function for Geolocation requires two callback functions, for success and errors, but I can't find an acceptable way to refer to the other functions that doesn't give a TYPE_MISMATCH_ERR that says those aren't functions.
onDeviceReady: function() {
//This line calls the geolocation function and specifies the two callback functions, but it thinks they don't exist
navigator.geolocation.watchPosition(this.onLocationSuccess, this.onLocationError, {enableHighAccuracy : true});
},
onLocationSuccess: function(position) {
// map logic
},
onLocationError: function(error) {
alert('code: ' +error.code+ '\n' + 'message: ' +error.message + '\n');
},
They're right below the onDeviceReady function, but for some reason it can't find them. this.onLocationSuccess doesn't work (even though that's how it refers to all of its other functions), self.onLocationSuccess doesn't work... Again, it seems like a matter of context, but I can't figure it out.
Any ideas? I don't even know enough about javascript to know if I'm explaining this adequately, so feel free to yell at me about that too.
The value of this in JavaScript depends on how a function is called. For example, this:
var obj = {
f: function() { console.log(this) }
};
obj.f();
will put obj in the console but this:
var obj = {
f: function() { console.log(this) }
};
var f = obj.f;
f();
will (usually) dump window into the console even though the same function is being called. This this behavior confuses almost everyone when they start using JavaScript. In your case, the documentation doesn't say anything about what this will be in the getPicture callbacks so this is probably going to be window instead of your object.
If you need a particular this with a callback function in JavaScript, you usually have to arrange it yourself. One way to do that is to stash the desired this in a variable and use an anonymous function:
var _this = this;
navigator.camera.getPicture(
function(imageData) { _this.onCameraSuccess(imageData) },
...
);
A cleaner way (especially when there are arguments to your functions) is to bind the function to the desired this using $.proxy, _.bind, Function.prototype.bind, or similar utilities that come with pretty much every JavaScript toolkit. For example, given this:
var obj = {
f: function() { console.log(this) }
};
var f1 = $.proxy(obj.f, obj);
var f2 = _(obj.f).bind(obj);
var f3 = obj.f.bind(obj);
Calling f1(), f2(), or f3() will all dump obj into the console. Underscore is the typical toolkit with Backbone so this would be common:
navigator.camera.getPicture(
_(this.onCameraSuccess).bind(this),
_(this.onCameraFail).bind(this),
{ ... }
);
Underscore also provides _.bindAll, this is commonly used within initialize to bind several functions in-place:
initialize: function() {
_.bindAll(this, 'onCameraSuccess', 'onCameraFail');
//...
}
and then you can use this.onCameraSuccess without worry about the binding:
navigator.camera.getPicture(
this.onCameraSuccess,
this.onCameraFail,
{ ... }
);
Using _.bindAll in initialize would probably be the most common approach in Backbone.
Since you are passing this as a reference to the camera function it is being overridden by camera's context with the current context of the application.
Make a clone of the current context like
var currentContext = this;
currentContext.onCameraSuccess,
currentContext.onCameraFail
This is applicable where ever the application control is moving from one context to other eg:backbone view to underscore functions and moving back to callback function etc.

Unable to use "class" methods for callbacks in JavaScript

I'm having a really rough time wrapping my head around prototypes in JavaScript.
Previously I had trouble calling something like this:
o = new MyClass();
setTimeout(o.method, 500);
and I was told I could fix it by using:
setTimeout(function() { o.method(); }, 500);
And this works. I'm now having a different problem, and I thought I could solve it the same way, by just dropping in an anonymous function. My new problem is this:
MyClass.prototype.open = function() {
$.ajax({
/*...*/
success: this.some_callback,
});
}
MyClass.prototype.some_callback(data) {
console.log("received data! " + data);
this.open();
}
I'm finding that within the body of MyClass.prototype.some_callback the this keyword doesn't refer to the instance of MyClass which the method was called on, but rather what appears to be the jQuery ajax request (it's an object that contains an xhr object and all the parameters of my ajax call, among other things).
I have tried doing this:
$.ajax({
/* ... */
success: function() { this.some_callback(); },
});
but I get the error:
Uncaught TypeError: Object #<an Object> has no method 'handle_response'
I'm not sure how to properly do this. I'm new to JavaScript and the concept of prototypes-that-sometimes-sort-of-behave-like-classes-but-usually-don't is really confusing me.
So what is the right way to do this? Am I trying to force JavaScript into a paradigm which it doesn't belong?
Am I trying to force JavaScript into a paradigm which it doesn't belong?
When you're talking about Classes yes.
So what is the right way to do this?
First off, you should learn how what kind of values the this keyword can contain.
Simple function call
myFunc(); - this will refer to the global object (aka window) [1]
Function call as a property of an object (aka method)
obj.method(); - this will refer to obj
Function call along wit the new operator
new MyFunc(); - this will refer to the new instance being created
Now let's see how it applies to your case:
MyClass.prototype.open = function() {
$.ajax({ // <-- an object literal starts here
//...
success: this.some_callback, // <- this will refer to that object
}); // <- object ends here
}
If you want to call some_callback method of the current instance you should save the reference to that instance (to a simple variable).
MyClass.prototype.open = function() {
var self = this; // <- save reference to the current instance of MyClass
$.ajax({
//...
success: function () {
self.some_callback(); // <- use the saved reference
} // to access instance.some_callback
});
}
[1] please note that in the new version (ES 5 Str.) Case 1 will cause this to be the value undefined
[2] There is yet another case where you use call or apply to invoke a function with a given this
Building on #gblazex's response, I use the following variation for methods that serve as both the origin and target of callbacks:
className.prototype.methodName = function(_callback, ...) {
var self = (this.hasOwnProperty('instance_name'))?this.instance_name:this;
if (_callback === true) {
// code to be executed on callback
} else {
// code to set up callback
}
};
on the initial call, "this" refers to the object instance. On the callback, "this" refers to your root document, requiring you to refer to the instance property (instance_name) of the root document.

Categories

Resources