How to access an instance variable within a Promise callback? - javascript

Let's say I have a basic dumb javascript class :
var FunctionX = function(configs) {
this.funcConfigs = configs;
}
FunctionX.prototype.getData = function() {
return $.get('/url');
}
FunctionX.prototype.show = function(promise) {
console.log(this.funcConfigs); // <-- this here is the promise itself, I'm looking to get the instance's configs
}
FunctionX.prototype.setup = function() {
this.GetData().then(show);
}
var f = new FunctionX({ "a": "b" });
f.setup();
Now I'm trying here in the show function to access the instance variable "funcConfig". "This" is the promise, and "funcConfigs" directly returns undefined.
I tried to resolve this issue with a .resolveWith(this) but it does not solve this issue.
How can I access the instances variables in this scope context?

In agreement with user2864740, the issue is most likely caused because this is not what you expect it to be when show is invoked as a callback. To make this work properly, you need to capture the proper this in a closure (e.g. var that = this;), and invoke it explicitly.
In other words...
FunctionX.prototype.setup = function() {
var that = this;
this.getData().then(function () {
that.show();
});
}
EDIT: For a slightly cleaner syntax (using underscore.js):
FunctionX.prototype.setup = function() {
var that = this;
this.getData().then(_.bind(this.show, this));
}

Related

Accessing 'this' of an object inside promise callback (then)

I want to create an object in Javascript.
One of the methods should execute a promise chain. Each of the methods in the chain have to access a config variable that is a member of the object.
The problem is, the this operator is changed in PromiseMethod2 and I can't access the config variable (It works correctly in PromiseMethod1).
Here's my code:
var SomeObject(config) {
var that = this;
that.config = config;
}
SomeObject.prototype.SomeMethod = function() {
var that = this;
that.PromiseMethod1()
.then(that.PromiseMethod2)
.catch(console.error);
}
SomeObject.prototype.PromiseMethod1 = function() {
var that = this;
config = that.config;
return SomePromise();
}
SomeObject.prototype.PromiseMethod2 = function(someParams) {
var that = this;
config = that.config;
params = someParams;
return SomePromise();
}
var someObject = new SomeObject(someConfig);
someObject.SomeMethod().then(function () {
console.log('Done!');
}
I want to use the method delegate in the chain instead of just executing:
that.PromiseMethod1().then(function(response) { return that.PromiseMethod2(that, response); };
I can't use the bind method because it looks like it gets rebinded when the callback is executed.
Is there a solution to this?
Why's there a difference between PromiseMethod1 and PromiseMethod2?
My real recommendation is not to use this or new at all (and you can use Object.create if you still want inheritance):
var SomeObject = function(config) {
return {
PromiseMethod1: function(){
return somePromise(config.foo);
},
PromiseMethod2: function(x){
return someOtherPromise(config.bar, x);
}
}
}
var instance = SomeObject({config: true});
instance.PromiseMethod1().then(instance.PromiseMethod2);
Here I'm using closures, and their ability to enclose variables of their parent lexical scope, to my advantage. Rather than relying on JavaScript to magically inject a this into my function at run-time based on which object the function is called on, because as demonstrated by your problem, that doesn't always work.
However, I know that its an unconventional way to work, so if you'd rather stick with this, you'll need to use bind in order to tell JavaScript which magical this-value the function belongs to:
var SomeObject function(config) {
this.config = config;
}
SomeObject.prototype.PromiseMethod1 = function(){
return somePromise(this.config.foo);
}
SomeObject.prototype.PromiseMethod1 = function(x){
return someOtherPromise(this.config.bar, x);
}
var instance = new SomeObject({config: true});
instance.PromiseMethod1().then(instance.PromiseMethod2.bind(instance)); //<- :(
In your example SomeMethod you're not actually using bind. You still need to bind because you're passing the function into .then(f), and the code which receives the function doesn't know anymore which object it should use for the this. Now look at my earlier recommended code again. There are no thisses in there, so those functions don't rely at all on which object they're being called on, you can pass them around as higher-order-functions as much as you want without ever having to bind or that = this. :)
I would say that's impossible. You try to mix 2 different approaches: method chaining and promises chaining. I'd recommend to review your architecture.
The only visible thing (but personally I don't like it) if you have a full control over all promises that you want to use is to pass config values through the whole promises chain.
SomeObject.prototype.init = function() {
var that = this;
return new Promise(function(resolve, reject) {
resolve(that.config)
});
}
SomeObject.prototype.PromiseMethod1 = function(config, params) {
return SomePromise(config, params);
}
SomeObject.prototype.PromiseMethod2 = function(config, someParams) {
return SomePromise(config, someParams);
}
SomePromise = function(config, params) {
return new Promise(function(resolve, reject) {
//some code here
resolve(config, newParamsFromSomeCode)
});
}
Then you'll be able to call:
that.init().then(that.PromiseMethod1).then(that.PromiseMethod2);
But again, it doesn't look like a good code...

`this` is undefined when calling method from another context

This is my first time creating OOP for JS. I followed some tutorials but I can't wrap my head around this issue. I know the problem, but i dont know the solution
function NewApp(name){
this.name = name;
this.currentPage = 1;
this.customObjectWithMethods = //init with options and so on
}
NewApp.prototype.logic = function(){
// Note 1.
var app = this
//Note 3.
this.customObjectWithMethods.method{
if(app.currentpage < 3)
// Note 2.
app.navigate(app.logic)
}
}
NewApp.prototype.navigate = function(sender){
var app = this;
this.customObjectWithMethods.method{
app.currentpage++;
this.method(function() {
return app.currentPage === 2;
}, sender(), this.terminate);
}
}
Note 1: I need to create a reference because after that, this doesn't
work anymore to refer to the current object.
Note 2: After the check I want to do some logic in another method and repeat the current function, but when the function runs again it breaks on the method (this.customObjectWithMethods) because this doesn't exists.
Note 3: This is where it breaks because "this" works the first time not the second time.
It gets very complicated like this with the this-keyword, which makes me think that my design may be flawed.
Is there any solution for this problem, or should I refactor it ?
Surely it will become complicated, thiskeyword doesn't always refer to the main object but to the scope where it is used, take a look at Scope and this in JavaScript for further information.
This is your way to go, make a variable that contains your constructor and add these two methods to this variable, after that you can call your functions:
var newApp = function newApp(name){
this.name = name;
this.currentPage = 1;
//Make a reference to your object here
var THIS = this;
this.logic = function(){
var sender = this;
THIS.customObjectWithMethods.method = function(){
if(THIS.currentpage < 3)
THIS.navigate(sender);
}
}
this.navigate = function(sender){
this.customObjectWithMethods.method = function(){
THIS.currentpage++;
this.method(function() {
return THIS.currentPage === 2;
}, sender(), this.terminate);
}
}
}
And this is how to use your constructor and its methods:
var app = newApp("Test");
//Call the first method
app.customObjectWithMethods();
//Thenn call the second one
app.logic();
Some syntax errors & style issues - here is a short correction
var myFunction = function(){
//code here
};
var mySecondFunction = function(){
//code here
};
function NewApp(name){
this.name = name;
this.currentPage = 1;
this.customObjectWithMethods = function(){}; //empty function so calling doesnt resolve in error
}
NewApp.prototype.logic = function(){
this.customObjectWithMethods.method = mySecondFunction.bind(this);
}
NewApp.prototype.navigate = function(sender){
this.customObjectWithMethods.method = myFunction.bind(this);
}
I have moved the 2 functions outside of the constructor Function so they dont get recreated every time you call the constructor functions.
with _.bind(this) the "this"-reference gets passed into the scope of your functions (i think this is more pretty than creating another var).
with
var reff = new NewApp('namename');
you can get started calling your functions now:
ref.logic();
maybe this approach works for you?

Invoke javascript function from string

I have the following code in my javascript module, however this requires me to make the functions visible to the outside world.
var mymodule = function() {
var self = null,
init = function () {
self = this;
$('.actionButton').click(function () {
var worklistId = $(this).data('worklistid'),
action = $(this).data('action');
self[action] && self[action](worklistId); //watchout methods marked as not used are used by this invocation
})
},
send = function () {
// some logic
},
finish = function () {
// some logic
},
delete = function () {
// some logic
};
return {
init: init,
send: send,
finish: finish,
delete: delete
};
}();
mymodule.init();
So the only thing I want to return in my module is the init function. However when I do this I cant invoke the functions, because the object (self) only contains the init function visible on the outside.
return {
init: init
};
Is there any solution to invoke my functions like this without making them visible to the outside world? Please no if else statements, because my workflow is bigger then the 3 actions in this example. I want to make my module as closed as possible because this reduces the dependencies.
Update
Here is a updated jsfiddle with one of the proposed solutions, however this is giving me another issue. http://jsfiddle.net/marcofranssen/bU2Ke/
Something like this would work:
var mymodule = function() {
var self = this;
init = function () {
$('.actionButton').click(function () {
var worklistId = $(this).data('worklistid'), action = $(this).data('action');
self[action] && self[action](worklistId); //watchout methods marked as not used are used by this invocation
})
}
self.send = function () {
console.log('send');
}
self.finish = function () {
console.log('finish');
}
self.delete = function (item) {
console.log('delete');
};
return {
init: init,
};
}();
mymodule.init();​
Here's the fiddle:
http://jsfiddle.net/yngvebn/SRqN3/
By setting the self-variable to this, outside the init-function, and attaching the send, finish and delete functions to self, you can use the self[action] syntax from within the init-function
Yes, there is an easy (but perhaps slightly messy) way you can do this without making the functions visible to the global object:
var privateFunctions = { deleter: deleter, send: send};
Then, instead of self[action]();, just do privateFunctions[action](); and you're good to go.
Note that I changed delete to deleter, because delete is a reserved keyword...
var mymodule = function() {
var self = {},
init = function () {
$('.actionButton').click(function () {
var worklistId = $(this).data('worklistid'),
action = $(this).data('action');
self[action] && self[action](worklistId); //watchout methods marked as not used are used by this invocation
})
};
self.send = function () {
// some logic
};
self.finish = function () {
// some logic
};
self.delete = function () {
// some logic
};
return{
init:init
}
}();
mymodule.init();
This should Work!!
Even if you return an object just with the init property and you populate the rest dynamically such that your module uses them, you would still be making them visible to the outside at runtime. Anyone who wants to debug your module would easily get to them.
You can still create anonymous methods at runtime and they would also be visible together with their implementation.
In your code example, it is vague what "self" really is. You should keep it simple, use encapsulated functions as "private" methods and return a "public" (or "privileged" as Crockford calls it) function that have access to them.
This is the YUI way of doing singletons with private functions and variables. Example pattern:
var mymodule = (function() {
var internal = {
'send': function() {},
'finish': function() {},
'delete': function() {}
};
return {
'init': function(action) {
// access to internals, f.ex:
if ( internal.hasOwnProperty(action) ) {
internal[action].call(this); // bring the caller context
}
}
};
}());
mymodule.init('send');

Locate variable of my prototype

I have an object in Javascript:
function MyObject(aField) {
this.field = aField;
}
MyObject.prototype.aFunction = function() {
return this.field;
}
Then
var anInstance = new MyObject("blah");
var s = anInstance.aFunction();
this works fine, but if I pass the function to another function:
callLater(anInstance.aFunction);
I don't control callLater and it's minified, but it seems that it's calling aFunction using call() or apply(). Therefore, this points to another object and field is undefined.
What's the best practice to avoid this situation I'm facing?
That's because you lost the value of this Try this instead:
callLater(function() { anInstance.aFunction() });
Explaination, Think of it this way
function MyObject(aField) {
this.field = aField;
}
function MyObject2(aField) {
this.field = aField;
}
MyObject.prototype.aFunction = someFunct;
MyObject2.prototype.aFunction = someFunct;
Now what does someFunct belong to?
Well try doing MyObject.prototype.aFunction === MyObject2.prototype.aFunction it'll be true!
You see the problem Therefore it needs to be called from the class and not just referenced by value.

Javascript function (type) to store & use data

I really never used a javascript function type or class before, I understand Java and Python, but not javascript. So, I build a class like this:
function FormStore (type) {
this.setup = () =>{
this.store = {};
this.ERR_LINE_PREFIX = '#err_';
this.NO_DISPLAY_CLASS = 'no-display';
this.settings = {
'myID':{'hide':false},
}
}
this.checkVal= () => {
var geoArr = ['id_xx','myID', (...)];
var id;
$.each( geoArr, function(val) {
id = geoArr[val];
console.log(this.store) //-> returns undefined, below line is error
if (!(this.store[id])) {
return false;
}
});
};
var FS = new FormStore();
FS.setup();
The store is filled by components on document.ready. There is a function that looks up if the aligned components (glyph, label, input) have some classes or values and for the specific component fills a dict: {label:false,glyph:false, input:false}. However, for some reason it doesn't matter. Even if I enter some values in to the store right away (in setup) or create them on the fly, in checkVal the store doesn't exist, it's undefined.
Please, anybody, what am I not understanding about javascript type and classes here? I am googling this a lot and trying to find good resources but, "javascipt variable class" (or type) just yields a lot of DOM manipulation.
edit
There is a context problem in checkVal, you are using a non-arrow (and not explicitly bound) callback function and trying to access this inside of it. Change that to an arrow function as well, and the parent context (this) will be preserved:
$.each( geoArr, (val) => {
id = geoArr[val];
console.log(this.store)
if (!(this.store[id])) {
return false;
}
});
And while you are at changing that section, it's not going to work. You will not get access to $.each's return value. You should rely on native array APIs for this task and use Array.every to determine if all geoArr items are in the store (assuming that's your goal):
// returns false if not all geoArr items are in the store
geoArr.every(id => this.store[id])
original
I don't see you calling checkVal() anywhere, but based on the error you are getting it is called prior to setup() (since setup initializes the store). You could solve that problem straight away by moving this.store = {} out of setup (right at the top), e.g.:
function FormStore(type) {
this.store = {};
...
Having said that, I would suggest either defining your methods on the prototype, or utilizing ES6 classes. Here is a simplified version of both:
ES5 class
function FormStore(type) {
// make sure user didn't forget new keyword
if (this === window) {
throw new Error('FormStore must be called with "new" keyword')
}
// initialize state, this is the constructor
this.type = type;
this.store = {};
// any other state the class manages
}
FormStore.prototype = {
setup: function() {
// do setup stuff
// "this" points to instance
console.log('setup', this.type)
},
checkVal: function() {
}
}
var formStore = new FormStore('foo')
console.log(formStore.store) // <-- not undefined
formStore.setup()
ES6 Class
class FormStore {
constructor(type) {
this.type = type;
this.store = {};
}
setup() {
console.log('setup', this.type)
}
checkVal() {
}
}
const formStore = new FormStore('bar')
console.log(formStore.store) // <-- not undefined
formStore.setup()
It has to do with scoping. Your $.each in checkVal has a normal function. Inside the function the scope if this is different. If you want to keep the original scope you could use a fat arrow function like you do when defining the methods.
this.checkVal= () => {
var geoArr = ['id_xx','myID', (...)];
var id;
$.each( geoArr, val => {
id = geoArr[val];
console.log(this.store) //-> returns undefined, below line is error
if (!(this.store[id])) {
return false;
}
});
}
When you run your original code and place a breakpoint on the line with console.log you can see in the inspector that this is set to the Window object and no longer points to your FormStore.
function FormStore () {
this.setup = function(){
this.store = {};
this.ERR_LINE_PREFIX = '#err_';
this.NO_DISPLAY_CLASS = 'no-display';
this.settings = {
'myID':{'hide':false},
}
}
this.checkVal= function(){
var geoArr = ['id_xx','myID'];
var id;
$.each( geoArr, function(val) {
id = geoArr[val];
console.log(this.store) //-> returns undefined, below line is error
if (!(this.store[id])) {
return false;
}
});
}
};
var FS = new FormStore();
FS.setup();
Works absolutely fine, the code you provided had a missing bracket and you were using some broken es6 syntax

Categories

Resources