Node Function Scope - javascript

I jumped into the deep end recently and have been slowly learning to swim. I'm working on a CLI for building out a simple text game world. That code is becoming a convoluted mess and so I have tried to recreate the error I am getting in a simpler form below.
Try as I might I can't seem to understand the best way to structure all of my functions. In my project I have a parser function that breaks input up and searches for a 'verb' to invoke via a try/catch block. When a verb i.e. 'look' runs it accesses my database module and sends a query based on several parameters to return the description of a room or thing. Because this is all asynchronous virtually everything is wrapped in a promise but I am leaving that out of this example. The following is not the actual project, just a simple recreation of the way I have my objects set up.
APP:
// ***********************
const player = require('./scope_test_player');
player.look();
player.water();
Module1:
// ***********************
const apple_tree = require('./scope_test_apple_tree');
module.exports = {
look: function(){
console.log(
'The apple tree is '+apple_tree.height+'ft tall and has '
+apple_tree.apples+' apples growing on it'
);
},
water: function() {
apple_tree.grow();
}
};
Module2:
// ***********************
const player = require('./scope_test_player');
module.exports = {
height: 10,
nutrition: 0.3,
apples: [],
fertilize: function(number) {
this.nutrition+=number;
},
grow: function() {
this.height+=this.nutrition;
}
};
In the above code I get 'TypeError: apple_tree.grow is not a function' from water or undefined from look. This is the bane of my existence and I have been getting this seemingly at random in my main project which leads me to believe I dont understand scope. I know I can require the module within the function and it will work, but that is hideous and would add hundreds of lines of code by the end. How do I cleanly access the functions of objects from within other objects?

You problem is that have a cyclic dependencies in your project and that you overwrite the exports property of the module. Because of that and the way node cachges required modules, you will get the original module.exports object in scope_test_player file and not the one you have overwritten. To solve that you need to write it that way:
// ***********************
const apple_tree = require('./scope_test_apple_tree');
module.exports.look = function() {
console.log(
'The apple tree is ' + apple_tree.height + 'ft tall and has ' + apple_tree.apples + ' apples growing on it'
);
};
module.exports.water = function() {
apple_tree.grow();
};
And
// ***********************
const player = require('./scope_test_player');
module.exports.height = 10;
module.exports.nutrition = 10;
module.exports.apples = [];
module.exports.fertilize = function(number) {
this.nutrition = +number;
};
module.exports.growth = function() {
this.height = +this.nutrition;
}
But this is a really bad design in gerenal and you should find another way how to solve that. You should always avoid loops/circles in your dependency tree.
UPDATE
In node each file is wrappted into load function in this way:
function moduleLoaderFunction( module, exports /* some other paramteres that are not relavant here*/)
{
// the original code of your file
}
node.js internally does something like this for a require:
var loadedModules = {}
function require(moduleOrFile) {
var resolvedPath = getResolvedPath(moduleOrFile)
if( !loadedModules[resolvedPath] ) {
// if the file was not loaded already create and antry in the loaded modules object
loadedModules[resolvedPath] = {
exports : {}
}
// call the laoded function with the initial values
moduleLoaderFunction(loadedModules[resolvedPath], loadedModules[resolvedPath].exports)
}
return loadedModules[resolvedPath].exports
}
Because of the cyclic require, the require function will return the original cache[resolvedPath].exports, the one that was initially set before you assinged your own object to it.

Is Module1 = scope_test_player and Module2 = scope_test_apple_tree?
Maybe you have a cyclic reference here?
APP requires scope_test_player
// loop
scope_test_player requires scope_test_apple_tree
scope_test_apple_tree requires scope_test_player
// loop
As I can see scope_test_apple_tree doesn't use player.
Can you try to remove:
const player = require('./scope_test_player');
from Module2 ?

There are a few issues to address.
Remove the player require in Module 2(scope_test_apple_tree.js):
const player = require('./scope_test_player')
It doesn't do any damage keeping it there but it's just unnecessary.
Also, replace =+ with += in fertilize and grow which is what I think you are going for.
I was able to run the code natually with those fixes.
If you want to refactor, I'd probably flatten out the require files and do it in the main file controlling the player actions and explicitly name the functions with what is needed to run it (in this case...the tree).
Keeping mostly your coding conventions, my slight refactor would look something like:
index.js
const player = require('./scope_test_player');
const apple_tree = require('./scope_test_apple_tree');
player.lookAtTree(apple_tree);
player.waterTree(apple_tree);
scope_test_player.js
module.exports = {
lookAtTree: function(tree){
console.log(
'The apple tree is '+tree.height+'ft tall and has '
+tree.apples.length+' apples growing on it'
);
},
waterTree: function(tree) {
tree.grow();
console.log('The apple tree grew to', tree.height, 'in height')
}
};
scope_test_apple_tree.js
module.exports = {
height: 10,
nutrition: 0.3,
apples: [],
fertilize: function(number) {
this.nutrition += number;
},
grow: function() {
this.height += this.nutrition;
}
};

Yes, I had circular dependencies in my code because I was unaware of the danger they imposed. When I removed them from the main project sure enough it started working again. It now seems that I'm going to be forced into redesigning the project as having two modules randomly referencing each other is going to cause more problems.

Related

RequireJS - Cannot Access External Module Function

I'm having an issue with RequireJS. Essentially, I'm not able to access a function defined inside another file from another one.
I need to do that because I want to export a given subset of functions like
define('submodule', [], function() {
let myFunction1 = function(){ return "Hello"; }
let myFunction2 = function(){ return " From"; }
let myFunction3 = function(){ return " Submodule!"; }
return {
myFunction1 : myFunction1,
myFunction2 : myFunction2,
myFunction3 : myFunction3,
};
});
And accessing them from another file
define('main', ['config', 'sub1', 'sub2', 'submodule'],
function(config, sub1, sub2, submodule) {
//Config
alert(config.conf);
//Submodule
let callSubmodule = function() {
alert(submodule.myFunction1() +
submodule.myFunction2() +
submodule.myFunction3());
}
//sub1
let callSub1 = function() {
alert(sub1.myFunction1());
}
//sub2
let callSub2 = function() {
alert(sub2.myFunction1());
}
});
The fact is that usually I'm able to do this with sub1 and
sub2, but, with submodule, I simply can't. I think it's somehow caused by the dependencies in require.config.js.
My require.config.js:
require(['common'], function () { //contains vendors
require(['config'], function () { //contains a js config file
require(['main'], function () { //main file
require(['sub1', 'sub2'], function () { //some subfiles
require(['submodule']);
});
});
});
});
For submodule.myFunction1() and othe two related functions I'm getting:
Uncaught (in promise) TypeError: Cannot read property 'myFunction1' of undefined
This is weird since I'm able to do that in other situations and I really can't understand why this is happening. For instance, I'm able to call sub1 and sub2 functions from main and other files but not submodule in particular.
Index.html
//Taken from Plunker
. . .
<script data-main="common" data-require="require.js#2.1.20" data-semver="2.1.20" src="http://requirejs.org/docs/release/2.1.20/minified/require.js"></script>
<script src="require.config.js"></script>
. . .
<button onclick = "callSubmodule()">Call Submodule</button>
<button onclick = "callSub1()">Call Sub1</button>
<button onclick = "callSub2()">Call Sub2</button>
common.js contains vendors, here's just an example
requirejs.config({
baseUrl : "",
paths : {
"jquery" : "http://code.jquery.com/jquery-latest.min.js"
}
});
sub1.js
define('sub1', ['submodule'], function(submodule) {
let myFunction1 = function(){ return "called sub1"; }
return {
myFunction1 : myFunction1
};
});
sub2.js
define('sub2', ['submodule'], function(submodule) {
let myFunction1 = function(){ return "called sub2"; }
return {
myFunction1 : myFunction1
};
});
I set up a Plunker with #SergGr help that tries to replicate application's structure but all the modules get undefined on click. On the real application this does not happen.
How can I solve this?
This is your code:
define('main', ['submodule'], function(submod) {
console.log(submodule.myFunction());
});
You have submod in the parameter list. But you then try to access submodule. Note that you return the function straight from your module (return myFunction), so your module has the value of the function myFunction and thus the module is what you should call. The code should be:
define('main', ['submodule'], function(submod) {
console.log(submod());
});
I Managed to solve this issue. Essentially, it was caused by a circular-dependency between the modules. So, a needed b and b needed a leading to one of them being undefined on the dependency resolution.
I found a solution to that on the answer provided by #jgillich at requirejs module is undefined.
So, I managed to solve using, in main
define('main', ['config', 'sub1', 'sub2', 'require'],
function(config, sub1, sub2, submodule, require) {
//Config
alert(config.conf);
//Submodule
let callSubmodule = function() {
alert(require('submodule').myFunction1() +
require('submodule').myFunction2() +
require('submodule').myFunction3());
}
});
As #jgillich said:
If you define a circular dependency ("a" needs "b" and "b" needs "a"), then in this case when "b"'s module function is called, it will get an undefined value for "a". "b" can fetch "a" later after modules have been defined by using the require() method (be sure to specify require as a dependency so the right context is used to look up "a"):
//Inside b.js:
define(["require", "a"],
function(require, a) {
//"a" in this case will be null if "a" also asked for "b",
//a circular dependency.
return function(title) {
return require("a").doSomething();
}
}
);
http://requirejs.org/docs/api.html#circular
The way you've named your modules I would expect they all came from a require config file. I would not expect that requirejs would know how to load those files without some sort of explicit compilation process. I also suspect that your server is returning something due to a 404 that JS is almost able to interpret without exploding.
Your setup seems and naming scheme seems quite strange. If you have the ability to start from scratch below are my recommendations.
Recommendations:
I'm noticing that you're using absolute paths. I highly recommend using relative paths for everything. There are many reasons for this.
Your data-main should be what you call "require.config.js". Your common.js is actually a require.config.js.
You load require.config.js (which is your main) separately using a script tag. You can do this but it's strange.
You can use the "commonjs" style syntax to require files without needing to use the array to define all your dependencies. I recommend that.
This is my recommendation for a set-up:
index.html
<script src="/js/config.js" />
<script src="http://requirejs.org/docs/release/2.1.20/minified/require.js" />
<script>
require('/js/main', function(main) {
main({});
});
</script>
/js/config.js
// setting requirejs to an object before its loaded will cause requirejs to use it as the config
window.requirejs = {
baseUrl : "/",
paths : {
"jquery" : "http://code.jquery.com/jquery-latest.min.js"
}
};
/js/main.js
define(function(require) {
const sum = require('./sum');
return (a, b) => sum(a, b);
});
/js/sum.js
define(function(require) {
return (a, b) => a + b;
});
Update (March 02, 2017)
Your plunker obviously will not work because you have direct calls from HTML to your module functions.
<button onclick = "callSubmodule()">Call Submodule</button>
<button onclick = "callSub1()">Call Sub1</button>
<button onclick = "callSub2()">Call Sub2</button>
RequireJS doesn't work that way. One of key purposes of RequireJS is to provide modules isolation and thus it just can't work that way: imagine if several different modules had functions callSubmodule.
To the best of my knowledge there is no way to bind calls from HTML back to the code in a RequireJS module, it should be other way around: module binds to HTML. And if you fix those issues, everything works fine for me as you can see at this fork of your plunker.
Old Answer
The bug is in your subModule.js
define('submodule', [], function() {
let myFunction = function(){ return "Hello"; }
//return myFunction; // old, wrong
return { myFunction: myFunction };
});
Even if you want to return just 1 function you should not return it as is, you should wrap it into an object and give it an explicit name.
P.S. if this is not your real issuse, please provide us real Minimal, Complete, and Verifiable example

Browserify require inside object

First question here and it is 3:30AM so please bear with me as I try to explain my question.
I'm in the situation that I need a very big javascript object. Good practice learns me that I shouldn't expose anything to the window object unless absolutely necessary.
This object I made requires some sort of customization (settings) and an object containing different kind of customization (levels). The first object is not that big, but the second one is becoming quite big and will grow bigger in the future.
I figured it would be nice to split these objects into different files and want to use browserify for it. I dont however want the two objects available within the window scope.
// settings.js
var settings = {
"setting1": "hellp",
"setting2": "world"
}
module.exports = settings;
//levels.js
var levels = {
"level1": {
name: "level1",
type: "flat",
},
"level2": {
name: "level2",
type: "hills"
} //about 10 more...
}
module.exports = levels;
//world.js
var world = function() {
var self = this;
var settings = require('settings.js');
var levels = require('levels.js');
self.test = function() {
console.log(settings['setting1'] + " " + settings['setting2']; console.log(levels['level1'].name);
}
return self;
}
I work with browserify and gulp to append this into one /dist/world.js but in a test html page it always shows "world is undefined", while it does work when I append the file myself.
I figured I did something wrong with browserify but the gulp task I use for it works on other projects:
function handleError(level, error) {
gutil.log(error.message);
process.exit(1);
}
// Convenience handler for error-level errors.
function onError(error) {
handleError.call(this, 'error', error);
}
gulp.task('browserify', function () {
var b = browserify({
entries: './main.js',
paths: ['./www-src'],
debug: true
});
return b.transform(p)
.bundle()
.on('error', onError)
.pipe(source('main.js'))
.pipe(buffer())
.pipe(gulp.dest('./dist/'));
});
basically what im calling is this in the index.html:
//index.html
var world = new world();
world.test()
and it throws the "world is undefined" error
Is anyone able to give me any pointers how to fix this issue? Would really appreciate any help!
update:
I know that IFFE means immediately invoked function expression, in other words a function that gets executed immediately. What I dont get is how to access this.

Custom browser actions in Protractor

The problem:
In one of our tests we have a "long click"/"click and hold" functionality that we solve by using:
browser.actions().mouseDown(element).perform();
browser.sleep(5000);
browser.actions().mouseUp(element).perform();
Which we would like to ideally solve in one line by having sleep() a part of the action chain:
browser.actions().mouseDown(element).sleep(5000).mouseUp(element).perform();
Clearly, this would not work since there is no "sleep" action.
Another practical example could be the "human-like typing". For instance:
browser.actions().mouseMove(element).click()
.sendKeys("t").sleep(50) // we should randomize the delays, strictly speaking
.sendKeys("e").sleep(10)
.sendKeys("s").sleep(20)
.sendKeys("t")
.perform();
Note that these are just examples, the question is meant to be generic.
The Question:
Is it possible to extend browser.actions() action sequences and introduce custom actions?
Yes, you can extend the actions framework. But, strictly speaking, getting something like:
browser.actions().mouseDown(element).sleep(5000).mouseUp(element).perform();
means messing with Selenium's guts. So, YMMV.
Note that the Protractor documentation refers to webdriver.WebDriver.prototype.actions when explaining actions, which I take to mean that it does not modify or add to what Selenium provides.
The class of object returned by webdriver.WebDriver.prototype.actions is webdriver.ActionSequence. The method that actually causes the sequence to do anything is webdriver.ActionSequence.prototype.perform. In the default implementation, this function takes the commands that were recorded when you called .sendKeys() or .mouseDown() and has the driver to which the ActionSequence is associated schedule them in order. So adding a .sleep method CANNOT be done this way:
webdriver.ActionSequence.prototype.sleep = function (delay) {
var driver = this.driver_;
driver.sleep(delay);
return this;
};
Otherwise, the sleep would happen out of order. What you have to do is record the effect you want so that it is executed later.
Now, the other thing to consider is that the default .perform() only expects to execute webdriver.Command, which are commands to be sent to the browser. Sleeping is not one such command. So .perform() has to be modified to handle what we are going to record with .sleep(). In the code below I've opted to have .sleep() record a function and modified .perform() to handle functions in addition to webdriver.Command.
Here is what the whole thing looks like, once put together. I've first given an example using stock Selenium and then added the patches and an example using the modified code.
var webdriver = require('selenium-webdriver');
var By = webdriver.By;
var until = webdriver.until;
var chrome = require('selenium-webdriver/chrome');
// Do it using what Selenium inherently provides.
var browser = new chrome.Driver();
browser.get("http://www.google.com");
browser.findElement(By.name("q")).click();
browser.actions().sendKeys("foo").perform();
browser.sleep(2000);
browser.actions().sendKeys("bar").perform();
browser.sleep(2000);
// Do it with an extended ActionSequence.
webdriver.ActionSequence.prototype.sleep = function (delay) {
var driver = this.driver_;
// This just records the action in an array. this.schedule_ is part of
// the "stock" code.
this.schedule_("sleep", function () { driver.sleep(delay); });
return this;
};
webdriver.ActionSequence.prototype.perform = function () {
var actions = this.actions_.slice();
var driver = this.driver_;
return driver.controlFlow().execute(function() {
actions.forEach(function(action) {
var command = action.command;
// This is a new test to distinguish functions, which
// require handling one way and the usual commands which
// require a different handling.
if (typeof command === "function")
// This puts the command in its proper place within
// the control flow that was created above
// (driver.controlFlow()).
driver.flow_.execute(command);
else
driver.schedule(command, action.description);
});
}, 'ActionSequence.perform');
};
browser.get("http://www.google.com");
browser.findElement(By.name("q")).click();
browser.actions().sendKeys("foo")
.sleep(2000)
.sendKeys("bar")
.sleep(2000)
.perform();
browser.quit();
In my implementation of .perform() I've replaced the goog... functions that Selenium's code uses with stock JavaScript.
Here is what I did (based on the perfect #Louis's answer).
Put the following into onPrepare() in the protractor config:
// extending action sequences
protractor.ActionSequence.prototype.sleep = function (delay) {
var driver = this.driver_;
this.schedule_("sleep", function () { driver.sleep(delay); });
return this;
};
protractor.ActionSequence.prototype.perform = function () {
var actions = this.actions_.slice();
var driver = this.driver_;
return driver.controlFlow().execute(function() {
actions.forEach(function(action) {
var command = action.command;
if (typeof command === "function")
driver.flow_.execute(command);
else
driver.schedule(command, action.description);
});
}, 'ActionSequence.perform');
};
protractor.ActionSequence.prototype.clickAndHold = function (elm) {
return this.mouseDown(elm).sleep(3000).mouseUp(elm);
};
Now you'll have sleep() and clickAndHold() browser actions available. Example usage:
browser.actions().clickAndHold(element).perform();
I think it is possible to extend the browser.actions() function but that is currently above my skill level so I'll lay out the route that I would take to solve this issue. I would recommend setting up a "HelperFunctions.js" Page Object that will contain all of these Global Helper Functions. In that file you can list your browser functions and reference it in multiple tests with all of the code in one location.
This is the code for the "HelperFunctions.js" file that I would recommend setting up:
var HelperFunctions = function() {
this.longClick = function(targetElement) {
browser.actions().mouseDown(targetElement).perform();
browser.sleep(5000);
browser.actions().mouseUp(targetElement).perform();
};
};
module.exports = new HelperFunctions();
Then in your Test you can reference the Helper file like this:
var HelperFunctions = require('../File_Path_To/HelperFunctions.js');
describe('Example Test', function() {
beforeEach(function() {
this.helperFunctions = HelperFunctions;
browser.get('http://www.example.com/');
});
it('Should test something.', function() {
var Element = element(by.className('targetedClassName'));
this.helperFunctions.longClick(Element);
});
});
In my Test Suite I have a few Helper files setup and they are referenced through out all of my Tests.
I have very little knowledge of selenium or protractor, but I'll give it a shot.
This assumes that
browser.actions().mouseDown(element).mouseUp(element).perform();
is valid syntax for your issue, if so then this would likely do the trick
browser.action().sleep = function(){
browser.sleep.apply(this, arguments);
return browser.action()
}

Reloading / reinitializing / refreshing CommonJS modules

I understand CommonJS modules are meant to load once. Lets say we have a Single Page application with hash based navigation, when we navigate to a previously loaded page the code is not re-run as it has already been loaded once which is what we want.
How can I get the content of the module to reload as though it wasn't already initialized? For example, if I have some data in local storage that changes, how can I run a function to update this data and/or what would be the best way to update this data in a previously loaded module?
Rather than exporting the content of your module directly, you can just wrap it in a function and then call that function each time you need that content. I use this pattern for any kind of initialization a module may need. Here's a simple (and silly) example:
// this module will always add 1 to n
module.exports = function(n) {
return 1 + n;
}
vs:
module.exports = function(n1) {
// now we wrapped the module and can set the number
return function(n2) {
return n1 + n2;
}
};
var myModule = require('that-module')(5);
myModule(3); // 8
And another example that contains changing data:
// ./foo
module.exports = {
foo: Date.now()
};
// ./bar
module.exports = function() {
return {
foo: Date.now()
};
};
// index.js
var foo = require('./foo');
var bar = require('./bar');
setInterval(function() {
console.log(foo.foo, bar().foo);
}, 500);

Backbone.Wreqr vs Javascript Object

What are the main benefits backbone.wreqr has over a js object, both cases having access to marionette's Event aggregator.
Wouldn't assigning/calling methods from an object work the same way as Commands / RequestResponse. To me i see no need to implement this other than giving semantic/readability a +1.
https://github.com/marionettejs/backbone.wreqr
Can someone please enlighten me, this is my first backbone (and modular) application.
The benefits are:
event and command handling is optional and you don't need to check manually yourself for undefineds
optionally multiple handlers for each event
lazy execution of commands (fire event first, register command later and it will immediately be executed)
you can define the scope of execution w/o using any additional methods like $.proxy, ...
It provides implementations of several common messaging patterns, including the Event Aggregator Pattern, Command Pattern, and Observer Pattern.
These patterns facilitate decoupling of implementations to reduce object dependencies. Consider a simple "Combat" style game consisting of a tank and several targets. Without messaging patterns, the tank needs to have explicit knowledge about the targets and how they work, and in fact cannot exist without the target definition:
var Tank = function(targets) { this.targets = targets };
Tank.prototype.fire = function() {
var self = this,
HpLoss = -500;
_.each(this.targets, function(target) {
if (self.isNear(target.coordinates) && target.canWithstand(HpLoss)) {
target.die();
}
}
var target1 = new Target(coordinatesA, armorA);
var target2 = new Target(coordinatesB, armorB);
var tank = new Tank([target1, target2]);
Using messaging patterns such as Observer, tank in the code above doesn't need knowledge of its targets; rather, the targets can determine for themselves whether they should die:
var Target = function() {}
Target.prototype.calculateDamage = function(coordinates, damage) {
if (this.isNear(coordinates) && !this.canWithstand(damage)) {
this.die();
}
}
var Tank = function() {};
Tank.prototype.fire = function() {
this.trigger('fire', { damage: 400, coordinates: this.location });
};
// Now Tank is entirely self-contained, and some external mediator can
// make things happen at will:
function main() {
var target1 = new Target(coordinatesA, armorA);
var target2 = new Target(coordinatesB, armorB);
var tank = new Tank();
target1.listenTo(tank, 'fire', target1.calculateDamage, target1);
target2.listenTo(tank, 'fire', target2.calculateDamage, target2);
tank.fire();
var target3 = new Target3(coordinatesB, armorB);
target3.listenTo(tank, 'fire', target3.calculateDamage, target3);
}

Categories

Resources