I have 2 js files in my node.js app
File a defines global object foo
In file b I can refer to foo.
But if file b is loaded before file a, then an error occurs.
I'm using node's fs module to read the files. I use readdir, and then forEach require on every file. On my system the files are always read alphabetically, so there is never any problem.
Can I depend on those files being read alphabetically?
You can sort your array of files by name instead to be sure.
fs.readdir(path, function(err, files) {
files.sort(function(a, b) {
return a < b ? -1 : 1;
}).forEach(function(file, key) {
// stuff
});
});
Because you're working with an array of files, you best bet is to sort the array then work through it; Only starting a new file read when the previous one has completed.
Ignoring for the moment the fact that global variables are usually a bad idea...:-)
If you already know the filepaths you need to load, you might wish to use a structure like:
var fs=require('fs'),
vm=require('vm');
/**
* loads and evals two initialization files in order
* #param {callback} onComplete called on init completion
* #param {callback} on Error called on error (optional)
* #return null
*/
function init_app(onComplete,onError){
var first_filepath = './file1.js',
second_filepath = './file2.js';
// read/eval first file
fs.readFile(first_filepath,function(first_err,first_result){
if (first_err) {
if(onError) {
onError(first_err.message);
return;
}
else {
throw first_err;
}
}
// evaluate first result
try {
vm.createScript(first_result).runInThisContext();
}
catch (e) {
err_msg='failed to eval source from first file:' + e.type;
if(onError) {
onError(err_msg);
return;
}
else {
throw err_msg;
}
}
// check for existence of foo global
if(foo == undefined) {
var msg='foo is undefined after first file read';
if(onError) {
onError(err_msg);
return;
}
else {
throw err_msg;
}
}
// read/eval second (dependent) file
fs.readFile(second_filepath, function(second_err,second_result){
if (second_err) {
if(onError) {
onError(second_err.message);
return;
}
else {
throw second_err;
}
}
// evaluate second, dependent result
try {
vm.createScript(second_result).runInThisContext();
}
catch (e) {
err_msg='failed to eval source from second file:' + e.type;
if(onError) {
onError(err_msg);
return;
}
else {
throw err_msg;
}
}
// trigger callback
if(onComplete) onComplete();
}); // end fs.readFile(second_filepath,...
}); // end fs.readFile(first_filepath,...
}; // end init_app()
Used as:
var onComplete = function(){ console.log('init complete'); },
onError = function(msg){ console.error(msg); };
init_app(onComplete,onError);
Then again, if you only load these files once at the start of your application, you should use require().
Related
I apologise in advance if this question is repeated elsewhere. I am at a loss as to how to correctly phrase the question to find the answer through research alone.
I have many external js files which each contain a js array. I can reference these files and pull them into my webpage easily enough, but accessing the array via a placeholder isn't working. If the file is named 'walberton.js' the array it contains is named 'walberton'.
myBoundary = 'walberton'
The following works as the placeholder for the array is implicitly stated (walbertonx):
function showHideBoundary(myBoundary) {
var boundarySourceFile = myBoundary + '.js';
if (typeof walbertonx == 'undefined') {
var poll;
var timeout = 100; // 10 seconds timeout
var s = document.createElement("script");
s.src = boundarySourceFile;
document.body.appendChild(s);
poll = function () {
setTimeout(function () {
timeout--;
if (typeof walbertonx !== 'undefined') {
// External file loaded
drawWSCountyBoundary(walbertonx);
} else if (timeout > 0) {
poll();
} else {
// External library failed to load
alert("Apologies. Unable to load Boundary at this time.");
}
}, 100);
};
poll();
} else if (walbertonx !== undefined && line === undefined && line.getMap() === null) {
drawWSCountyBoundary(walbertonx);
} else if (walbertonx !== undefined && line !== undefined && line.getMap() !== null) {
line.setMap(null);
} else {
line.setMap(map);
}
}
The idea is code-reuse for all 163 boundary files I have. So, in the same way as I can reference the file with myBoundary, I can replace walbertonx with the actual variable once it has successfully loaded. The trick is I need to know what to check for before it's loaded and once it's loaded use that rather than the placeholder.
Code in the global scope should be accessible to all files loaded after it. Ensure that you include your 'walberton.js' file first. Afterwards you should be able to access the array granted it's in the global scope.
You could also attach the array to the window object if that suits your fancy, like so:
Window.walberton = walberton
Then you can access it in your secondary file like this
alert(Window.walberton)
If it is that you want to dynamically add them, consider having a global object array on the window
Window.fileArrays['walberton'] = walberton
Window.fileArrays['other'] = other
You'd ensure you do this in each file where you want to add an array.
Then you can iterate over them. Just ensure you detach them from the window when you're done.
This problem is in regards the creation of a Node module that depends on a async function to return the content. For instance, "src/index.js" is the following:
GOAL
The module A, implemented from "src/index" must be resolved and must not depend on promises, or anything else... It will just return a JSON object of computed values.
var a = require("./src/index");
// should be resolved already.
console.log(a.APP_NAME)
src/index.js
"use strict";
var CoreClass = require("./core-class");
var coreInstance = new CoreClass();
coreInstance.toJson(function(err, coreData) {
if (err) {
console.log("Error while loading " + __filename);
console.log(err);
return;
}
console.log(coreData);
// Export the data from the core.
module.exports = coreData;
});
src/core-class.js
The implementation of the method "toJson()", defined in the class in the file "src/core-class.js" is as follows:
/**
* #return {string} Overriding the toStrng to return the object properties.
*/
ISPCore.prototype.toJson = function toJson(callback) {
var result = {
// From package.json
APP_NAME: this.appPackageJson.name.trim(),
APP_VERSION: this.appPackageJson.version.trim(),
APP_CONFIG_DIR: this.APP_DIR + "/config",
APP_DOCS_DIR: this.APP_DIR + "/docs",
APP_TESTS_DIR: this.APP_DIR + "/tests",
};
// TODO: Remove this when we have a registry
if (!this.pom) {
// Let's verify if there's a pom.xml file in the roort APP_DIR
var _this = this;
this.APP_POM_PATH = this.APP_DIR + "/pom.xml";
// Check first to see if the file exists
fs.stat(this.APP_POM_PATH, function(err, fileStats) {
// The file does not exist, so we can continue with the current result.
if (err) {
return callback(null, result);
}
_this._loadPomXmlSettings(function pomXmlCallback(err, pomObject) {
if (err) {
return callback(err);
}
_this.pom = pomObject;
// Update the result with the pom information
result.POM_GROUPID = _this.pom.groupid || "undefined";
result.POM_ARTIFACTID = _this.pom.artifactid || "undefined";
result.POM_VERSION = _this.pom.version || "undefined";
// Callback with the updated version.
return callback(null, result);
});
});
} else {
result.POM_GROUPID = this.pom.groupid || "undefined";
result.POM_ARTIFACTID = this.pom.artifactId || "undefined";
result.POM_VERSION = this.pom.version || "undefined";
// Return just what's been collected so far, including the pom.
return callback(null, result);
}
};
Test class
Requiring this and trying to use the library just returns an empty object. Here's the test class...
// describing the method to get the instance.
describe("require(sp-core) with pom.xml", function() {
var core = null;
before(function(done) {
// Copy the fixture pom.xml to the APP_DIR
fs.writeFileSync(__dirname + "/../pom.xml", fs.readFileSync(__dirname + "/fixture/pom.xml"));
// Load the library after the creation of the pom
core = require("../src/");
console.log("TEST AFTER CORE");
console.log(core);
done();
});
after(function(done) {
// Delete the pom.xml from the path
fs.unlinkSync(__dirname + "/../pom.xml");
done();
});
it("should load the properties with pom properties", function(done) {
expect(core).to.be.an("object");
console.log("Loaded pom.xml metadata");
console.log(core);
expect(core.POM_ARTIFACTID).to.exist;
expect(core.POM_VERSION).to.exist;
done();
});
});
Execution of the tests
However, after a while, the output from the library shows up in the console.
SPCore with pom.xml
require(sp-core) with pom.xml
TEST AFTER CORE
{}
Loaded pom.xml metadata
{}
1) should load the properties with pom properties
{ APP_NAME: 'sp-core',
APP_VERSION: '0.3.5',
ENV: 'development',
NODE_ENV: 'development',
IS_PROD: false,
APP_DIR: '/home/mdesales/dev/isp/sp-core',
APP_CONFIG_DIR: '/home/mdesales/dev/isp/sp-core/config',
APP_DOCS_DIR: '/home/mdesales/dev/isp/sp-core/docs',
APP_TESTS_DIR: '/home/mdesales/dev/isp/sp-core/tests',
POM_GROUPID: 'com.mycompany',
POM_ARTIFACTID: 'my-service',
POM_VERSION: '1.0.15-SNAPSHOT' }
0 passing (142ms)
1 failing
1) SPCore with pom.xml require(sp-core) with pom.xml should load the properties with pom properties:
AssertionError: expected undefined to exist
How to properly create a module that depends on an Async call?
I'm sure this is due to the asynchronous call, but I was thinking that the module would not return {}, but wait until the callback returns.
I tried using:
Async.waterfall
Deasync (does not work)
Async.waterfall attempt
"use strict";
var async = require("async");
var CoreClass = require("./core-class");
var coreInstance = new CoreClass();
async.waterfall([
function(cb) {
coreInstance.toJson(cb);
},
function(coreData) {
console.log(coreData);
module.exports = coreData;
}
]);
Please please help!
Following the comments, I revisited the attempt of using "deasync" module, and it WORKS! YES WE CAN! Cheating with the hack of "deasync" :D
Runnable instance
The runnable solution is at http://code.runnable.com/VbCksvKBUC4xu3rd/demo-that-an-async-method-can-be-returned-before-a-module-exports-is-resolved-for-node-js-deasync-pom-parser-and-stackoverflow-31577688
Type "npm test" in the terminal box and hit "ENTER" (always works).
Just click in the "Run" button to see the execution of the code. All the source code is available. (Sometimes the container gets corrupted and the test fails).
Solution
Here's the implementation of the "GOAL" module.
/** #module Default Settings */
"use strict";
var CoreClass = require("./core-class");
var merge = require("merge");
var deasync = require("deasync");
// Core properties needed.
var coreInstance = new CoreClass();
var coreProperties = coreInstance.toJson();
// Pom properties temporary support, deasync the async call
var loadPom = deasync(coreInstance.loadPomXmlSettings);
var pomObject = loadPom(coreProperties.APP_POM_PATH);
// Merge them all.
var allProperties = merge(coreProperties, pomObject);
module.exports = allProperties;
With that, all the code is returned as expected for the module.exports!
Our extension (Addon SDK) looking for new files in folder C:\scan and send it to server. Every second extension look for latest file creation time and defined it as latest.(compare new file creation time and file creation time 1 sec ago.)
Files put to C:\scan from scanner Brother 7050 on Windows 7.
But sometimes into console.error we see:
Exception
message: "Component returned failure code: 0x8052000e (NS_ERROR_FILE_IS_LOCKED)
[nsIFileInputStream.init]",
result: 2152857614,
name: "NS_ERROR_FILE_IS_LOCKED"
I think Brother 7050 application have no time to unlock file before our extension can start to read it.
Q: How we can read latest file in folder true way without read file lock error?
/*
adr- folder path
array2 - array for search
mode - search or not search in array2 (0-1)
*/
function getfilelist(adr,array2, mode)
{
filelist2=[];
filelist2[0]="";
filelist2[1]=0;
var file = new FileUtils.File(adr);
var enumerator = file.directoryEntries;
while (enumerator.hasMoreElements())
{
inner = enumerator.getNext().QueryInterface(Ci.nsIFile);
if (inner.isFile())
{
namearray=inner.leafName.split(".");
r=namearray[namearray.length-1];
if (r=="jpg" || r=="jpeg")
{
if (mode==0)
{
if (inner.lastModifiedTime>filelist2[1])
{
filelist2[0]=inner.leafName;
filelist2[1]=inner.lastModifiedTime;
}
}
else if (mode==1)
{
if (inner.lastModifiedTime>array2[1] && inner.isReadable()==true)
return inner.leafName;
}
}
}
}
if (mode==0)
{
return filelist2;
}
return false;
}
The reason why you see NS_ERROR_FILE_IS_LOCKED is most likely that the file is still being written and you are trying to access it too early. However, it is also possible that some other software immediately locks the file to check it, e.g. your anti-virus.
Either way, there is no way to ignore the lock. Even if you could, you might get an incomplete file as a result. What you should do is noting that exception and remembering that you should try to read that file on next run. Something along these lines:
var {Cr} = require("chrome");
var unaccessible = null;
setInterval(checknewfiles, 1000);
function checknewfiles()
{
var files = getfilelist(...);
if (unaccessible)
{
// Add any files that we failed to read before to the end of the list
files.push.apply(files, unaccessible);
unaccessible = null;
}
for (var file of files)
{
try
{
readfile(file);
}
except(e if e.result == Cr.NS_ERROR_FILE_IS_LOCKED)
{
if (!unaccessible)
unaccessible = [];
unaccessible.push(file);
}
}
}
For reference:
Components.results
Chrome authority
Conditional catch clauses
for..of loop
I am writting a modest firefox add-on and I have some problems getting the results used inside the "flow" of the add-on script.
I have the code taking care of querying a sqlite database as a module but I don't know how to create a callback inside of it so that the pagemod in the add-on script can use it and pass it to the content script.
Basically here is what I have:
main.js :
var pageMod = require("sdk/page-mod");
var self = require("sdk/self");
var myDbScript = require('./myDbScript');
pageMod.PageMod({
include: "*.example.com/*",
contentScriptFile: [self.data.url('jquery-1.10.2.min.js'),
self.data.url('myContentScript.js')],
onAttach: function(worker) {
// Query the database on behalf of the content script
worker.port.on('queryTheDB', function(message) {
// Get the data from the DB (é is some test value here)
// Not working because asynchronous querying of the DB
var resultFromDB = myDbScript.getResult(2);
// Send the result to the content script
worker.port.emit('hereIsYourResult', resultFromDB);
});
}
});
myDBScript.js
// Get required components
var {components} = require("chrome");
components.utils.import("resource://gre/modules/FileUtils.jsm");
components.utils.import("resource://gre/modules/Services.jsm");
// Some code to get the DB
// Create statement to retrieve country based on the IP
var statement = dbConnection.createStatement("SELECT col1, col2 FROM table WHERE col1 = :given_col1");
function getResult(submittedValue) {
// Bind parameters
statement.params.given_col1 = submittedValue;
// Execute
statement.executeAsync({
handleResult: function(aResultSet) {
for (let row = aResultSet.getNextRow();
row;
row = aResultSet.getNextRow()) {
var resultFromDB = row.getResultByName("col2");
}
},
handleError: function(aError) {
print("Error: " + aError.message);
return 'error';
},
handleCompletion: function(aReason) {
if (aReason != components.interfaces.mozIStorageStatementCallback.REASON_FINISHED) {
print("Query canceled or aborted!");
return 'canceledOrAborted';
} else {
// Sending the result to the add-on script so that it can
// pass it to the content script
notifyingTheAddonScript(resultFromDB);
}
}
});
}
// Enable the use of the getResult function
exports.getResult = getResult;
The thing is that I don't see how to have the addon script be aware that the result is ready. Please bear with me, I am a noob at this...
Since I don't have the full source, I cannot test. So you'll have to fix any I made errors yourself ;)
First, lets add a callback.
// #param {function(result, error)} callback
// Called upon query completion.
// if |error| is a string, then the query failed.
// Else |result| will contain an array of values.
function getResult(submittedValue, callback) { // changed
// Bind parameters
statement.params.given_col1 = submittedValue;
var rv = [], err = null; // added
// Execute
statement.executeAsync({
handleResult: function(aResultSet) {
for (let row = aResultSet.getNextRow();
row;
row = aResultSet.getNextRow()) {
rv.push(row.getResultByName("col2")); // changed
}
},
handleError: function(aError) {
print("Error: " + aError.message);
err = aError.message; // changed
},
handleCompletion: function(aReason) {
if (aReason != components.interfaces.mozIStorageStatementCallback.REASON_FINISHED) {
print("Query canceled or aborted!");
err = err || 'canceled or aborted'; // changed
}
callback(err ? null : rv, err); // replaced
}
});
}
Lets use this stuff now in the pagemod
onAttach: function(worker) {
// Query the database on behalf of the content script
worker.port.on('queryTheDB', function(message) {
// Get the data from the DB (é is some test value here)
// Not working because asynchronous querying of the DB
myDbScript.getResult(2, function callback(result, error) {
if (error) {
worker.port.emit("hereIsYourError", error);
return;
}
worker.port.emit("hereIsYourResult", result);
});
});
}
You might want to take some precautions not to fire multiple queries. While it would be OK to do so, it might hurt performance ;)
Since our callback already looks kinda like a promise, it might actually be a good idea to use promises, maybe even with the Sqlite.jsm module and some Task.jsm magic.
What event-listeners can I use to identify requests originating from the hiddenDOMWindow (or an iframe within it) in a firefox-addon? I need to do this BEFORE the request has been sent, in the "http-on-modify-request" event, for example.
What I've tried:
register for the global "http-on-modify-request"; but I can't distinguish the source window
add listener to the hiddenDOMWindow itself; but I can't find any before-load-event
add listener to the hiddenDOMWindow.document; no before-load-event
add listener to the created hiddenDOMWindow.document.iframe; no before-load-event
First, you need to get a DOMWindow from an nsIChannel:
function getDOMWindowFromChannel(ch) {
var wp;
try {
if (ch.loadGroup && ch.loadGroup.groupObserver) {
wp = ch.loadGroup.groupObserver.
QueryInterface(Ci.nsIWebProgress);
}
} catch (ex) {}
try {
if (!wp) {
wp = ch.notificationCallbacks.
getInterface(Ci.nsIWebProgress);
}
}
catch (ex) {}
try {
if (wp) {
return wp.DOMWindow || null;
}
}
catch (ex) {}
return null;
}
Now that you got a DOMWindow, you need to find the top level window for that DOMWindow, which is not really intuitive:
function getToplevelWindow(win) {
try {
return win.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIWebNavigation).
QueryInterface(Ci.nsIDocShell).
treeOwner.
QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIXULWindow).
docShell.
contentViewer.DOMDocument.defaultView;
}
catch (ex) {
// Likely already a top-level window.
return win;
}
}
Now, lets craft and install the observer, bringing all together:
function observe(channel, topic, data) {
if (!(channel instanceof Ci.nsIChannel)) {
return;
}
var win = getDOMWindowFromChannel(channel);
if (!win) {
return;
}
var topWin = getToplevelWindow(win);
if (topWin.location.href.indexOf("chrome://browser/content/hiddenWindow") != 0) {
return;
}
// do stuff, e.g.
console.log(topWin.location.href);
}
Services.obs.addObserver(observe, "http-on-modify-request", false);
It should be noted that not all requests are nsIChannel and not all nsIChannel actually have a DOMWindow or real loadGroup associated (e.g. background requests), hence all those try catch blocks.
Also, don't forget to remove the observer again at some point, which I skipped. ;)
And lastly, here is some code to actually test this (I ran the whole thing as a Scratchpad on an about:newtab tab, which happens to have chrome privileges just like add-ons):
var hw = Services.appShell.hiddenDOMWindow;
var iframe = hw.document.createElement("iframe");
hw.document.documentElement.appendChild(iframe);
var r = iframe.contentWindow.XMLHttpRequest();
r.open("GET", "http://example.org/");
r.send();