Using callback function with prototype functions - javascript

I am having trouble figuring out how to pass the objects method rather than sort "generic prototype" method when doing callback.
function Client() {
this.name = "hello";
}
Client.prototype.apiCall = function(method, params, callback) {
callback();
}
Client.prototype.onLogin = function(error, data) {
console.log(this.name);// undefined!!!!
}
Client.prototype.start = function() {
var self = this;
self.apiCall('rtm.start', {
}, self.onLogin) // passing of method like this does not work.
}
I am passing the onLogin method but well it does not work. This is code I have re-written. Previously I nested all methods inside the Client function but well, I learned that that is not the way to do it so now I am trying using prototype.
I know there is some solution "binding" the onLogin function inside the Client() function but well I want to understand the issue.

You need to bind the apiCalls context to the callback using bind:
Client.prototype.apiCall = function(method, params, callback) {
var bound = callback.bind(this);
bound();
}
Otherwise, the this within onLogin is set to the global object.
See Call, Apply And Bind for further details.
Basically .bind(obj) returns a function which, when called, will internally use (obj) as this.
So you create this bound and then you call it.

You can use call or apply to bind this, see snippet. I've modified your code for demonstration purposes. Hope it clarifies things for you
function Client() {
this.name = "hello";
}
Client.prototype = {
apiCall: function(method, params, callback) {
try {
var trial = method.call(this, params);
callback.apply(this, [null, trial]);
} catch (e) {
callback.apply(this, [e, null]);
}
},
onLogin: function(error, data) {
if (error) {
Helpers.report('<b style="color: red">' +
'An error occured!</b> <i>' +
error.message + '</i>')
} else {
Helpers.report(this.name, ' (data.result = ' + data.result + ')');
}
},
start: function() {
Helpers.useCSS(1);
// error from this.rtm.start
Helpers.report('Command: <code>', 'this.apiCall(this.rtm.start, {will: \'not work\'}, this.onLogin);','</code>');
this.apiCall(this.rtm.start, {will: 'not work'}, this.onLogin);
// this.rtm.works is ok
Helpers.report('<br>Command: <code>',
'this.apiCall(this.rtm.works, {will: \'work\'}, this.onLogin);',
'</code>');
this.apiCall(this.rtm.works, {
will: 'work'
}, this.onLogin);
},
// --------------------------------
// added rtm for snippet demo
rtm: {
start: function(params) {
return anerror;
},
works: function(params) {
return {
result: 'worked, <code>params: ' + JSON.stringify(params) + '</code>'
};
}
},
};
new Client().start(); //<= here
<script src="https://rawgit.com/KooiInc/Helpers/master/Helpers.js"></script>

Related

Calling a Node.js module function from within the module

I'm attempting to write a node module in order to clean up my code and separate it out into different files.
Consider the below code:
module.exports = {
Hello : function(request, reply) {
return reply("Hello " + World());
},
World : function() {
return "World";
}
}
If I import the above module and use the Hello function as handler for a specific route, i get an HTTP 500 internal server error.
I've narrowed the problem down to the call to World(), if I change the Hello function to
Hello : function(request, reply) {
return reply("Hello World");
}
Then it works fine, so it seems that it is being tripped up when calling another function from within the export object
Does anyone know why this is happening and how to resolve it?
You should call it as follows:
module.exports = {
Hello: function(request, reply) {
return reply("Hello " + module.exports.World());
},
World: function() {
return "World";
}
}
If you are aiming for cleaner code, I suggest that you change the code to this:
function World() {
return "World";
}
function Hello(request, reply) {
return reply("Hello " + World());
}
module.exports = {
Hello,
}
This will make your code more readable and you will only be exporting what you actually need. This question has other solutions to your issue.
Well let's demonstrate the this
this doesn't define the object that the function resides in. It defines from where the function is called. So while;
var obj = {
Hello : function(request, reply) {
return reply("Hello " + this.World());
},
World : function() {
return "World";
}
};
obj.Hello("test", console.log);
would work just fine; This wouldn't;
var obj = { Hello : function(request, reply) {
return reply("Hello " + this.World());
},
World : function() {
return "World";
}
};
setTimeout(obj.Hello,100,"test",console.log);
This is just because the obj.Hello will be assigned an argument in setTimeOut function's definition and that argument will be invoked as window being the this for that function. So you should instead do like;
var obj = { Hello : function(request, reply) {
return reply("Hello " + this.World());
},
World : function() {
return "World";
}
};
setTimeout(obj.Hello.bind(obj),100,"test",console.log);
//or
setTimeout(obj.Hello.bind(obj,"test",console.log),100);
//or
setTimeout((x,y) => obj.Hello(x,y),100,"test",console.log);
You'll need to add this to your invocation of World() -
module.exports = {
Hello : function(request, reply) {
return reply("Hello " + this.World());
},
World : function() {
return "World";
}
}
World is an attribute of the export object, not a variable within the accessible scope - so you need to specify that the function belongs to this.

How did `getService` in `injector.js` access local variable from its caller in angular?

Consider the following code in the official angular repository
function createInjector(modulesToLoad, strictDi) {
strictDi = (strictDi === true);
var testingScope = 'this is a test';
var INSTANTIATING = {},
providerSuffix = 'Provider',
path = [],
loadedModules = new HashMap([], true),
providerCache = {
$provide: {
provider: supportObject(provider),
factory: supportObject(factory),
service: supportObject(service),
value: supportObject(value),
constant: supportObject(constant),
decorator: decorator
}
},
providerInjector = (providerCache.$injector =
createInternalInjector(providerCache, function(serviceName, caller) {
if (angular.isString(caller)) {
path.push(caller);
}
throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
})),
instanceCache = {},
protoInstanceInjector =
createInternalInjector(instanceCache, function(serviceName, caller) {
var provider = providerInjector.get(serviceName + providerSuffix, caller);
return instanceInjector.invoke(
provider.$get, provider, undefined, serviceName);
}),
instanceInjector = protoInstanceInjector;
providerCache['$injector' + providerSuffix] = { $get: valueFn(protoInstanceInjector) };
var runBlocks = loadModules(modulesToLoad);
instanceInjector = protoInstanceInjector.get('$injector');
instanceInjector.strictDi = strictDi;
forEach(runBlocks, function(fn) { if (fn) instanceInjector.invoke(fn); });
return instanceInjector;
And
function createInternalInjector(cache, factory) {
function getService(serviceName, caller) {
console.log(testingScope);
if (cache.hasOwnProperty(serviceName)) {
if (cache[serviceName] === INSTANTIATING) {
throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
serviceName + ' <- ' + path.join(' <- '));
}
return cache[serviceName];
} else {
try {
path.unshift(serviceName);
cache[serviceName] = INSTANTIATING;
return cache[serviceName] = factory(serviceName, caller);
} catch (err) {
if (cache[serviceName] === INSTANTIATING) {
delete cache[serviceName];
}
throw err;
} finally {
path.shift();
}
}
}
return {
get: getService,
};
}
We see that createInternalInjector is able to access local variables (like path) in its caller's scope createInjector without passing in to the parameter.
Indeed if I add testingScope to createInjector and try to access in createInternalInjector, I was able to do it.
This is weird because I try to replicate this behavior like the following.
testOuter();
function testOuter() {
var outer = 'outer'
testInner().test();
}
function testInner() {
function testing() {
console.log(outer);
}
return {
test: testing
}
}
But got an error instead.
ReferenceError: outer is not defined
Can someone give me some pointers on why this is happening?
The issue here is scope chaining. createInternalInjector function is called within createInjector function, therefore it can access its parent properties. In your case you have sibling functions that are unaware to each other variables.
More on scope chains and closures: https://stackoverflow.com/a/1484230/5954939

Parenting this in Javascript

I'm trying to make the following code works without any luck, and I can't see a clear solution on how to do it.
export default {
model: null,
set: function (data) {
this.model = data
},
account: {
update: function (data) {
this.model.account = data
}
}
}
My issue here is that account.update fails because this.model does not exists. I suspect that the sub object gets a new this, hence my issue, but I don't know how to fix it.
I tried the alternative here :
export default (function () {
let model = null
function set (data) {
this.model = data // I also tried without the `this.` but without any luck too
},
function updateAccount(data) {
this.model.account = data
}
return {
'model': model,
'set': set,
'account': {
'update': updateAccount
}
}
})()
But apparently the same rule applies.
Maybe it's worth noting that I'm using Babel to compile ES6 down to ES5 javascript.
It fails because this refers (in this case) to the window object. Reference the object itself like this:
let myModel = {
model: null,
set: function (data) {
myModel.model = data // reference myModel instead of this
},
account: {
update: function (data) {
myModel.model.account = data // reference myModel instead of this
}
}
}
I would take an approach similar to your alternative solution. There is however no need to wrap your code in an IIFE, ES2015 modules are self-contained; you don't need an IIFE for encapsulation.
let model = null,
set = (data) => {
model = data;
},
updateAccount = (data) => {
if (!model) {
throw('model not set');
}
model.account = data;
};
export default {
model,
set,
account: {
update: updateAccount
}
};
Since you are already using Babel, I also used arrow functions and the new shorthand properties to make the code a little shorter/readable.

How do I chain Intern Page Object function calls?

Following the Intern user guide, I wrote a simple page object:
define(function(require) {
function ListPage(remote) {
this.remote = remote;
}
ListPage.prototype = {
constructor: ListPage,
doSomething: function(value) {
return this.remote
.get(require.toUrl('http://localhost:5000/index.html'))
.findByCssSelector("[data-tag-test-id='element-of-interest']")
.click().end();
}
};
return ListPage;
});
In the test, I want to call doSomething twice in a row, like this:
define(function(require) {
var registerSuite = require('intern!object');
var ListPage = require('../support/pages/ListPage');
registerSuite(function() {
var listPage;
return {
name: 'test suite name',
setup: function() {
listPage = new ListPage(this.remote);
},
beforeEach: function() {
return listPage
.doSomething('Value 1')
.doSomething('Value 2');
},
'test function': function() {
// ...
}
};
});
});
However, when I run the test, I get this error:
TypeError: listPage.doSomething(...).doSomething is not a function
I tried some approaches described in this question, to no avail.
A better way to implement page objects with Intern is as helper functions rather than Command wrappers. Groups of related helper functions can then be used to create Page Object modules.
// A helper function can take config parameters and returns a function
// that will be used as a Command chain `then` callback.
function doSomething(value) {
return function () {
return this.parent
.findByCssSelector('whatever')
.click()
}
}
// ...
registerSuite(function () {
name: 'test suite',
'test function': function () {
return this.remote.get('page')
// In a Command chain, a call to the helper is the argument
// to a `then`
.then(doSomething('value 1'))
.then(doSomething('value 2'));
}
}

Javascript unit testing - stub the data in a success callback

I'm writing a javascript test for my UserRepository.
I want to stub the data in the success callback function of the $http object.
User Repository code:
function UserRepository($http) {
return {
getUsers: function () {
$http({ url: '/GetUsers' }).success(function (data) {
//populate users
});
return users;
}
};
}
My test code:
var httpStub = function() {
return new {
success: function(callback) {
var array = [];
array.push({ forename: 'john', surname: 'smith' });
callback(array);
}
};
};
var userRepository = new UserRepository(httpStub);
userRepository.getUsers();
The error i'm getting is "the object is not function" which i think is happening where my httpstub returns the object literal containing the success function, but I can't figure out how to fix it.
I've fixed the problem. The httpStub was doing
return new{
success : function .....
}
When there should have been no new keyword e.g:
return {
success : function .....
}

Categories

Resources