const usage inside for loop, why this behaviour? - node.js

I have a for loop in my nodejs code
const saveDocument = co.wrap(function *(documentData, user, locale) {
var now = moment();
var creationDateLongString = now.format("YYYYMMDDHHmmss");
var creationDateShortString = now.format("YYYYMMDD");
var outputChildFolder = documentData.code + '_' + creationDateLongString + '_' + documentCounter;
var outputFolder = config.files.incomingDocumentsDir + '/' + outputChildFolder;
++documentCounter;
yield fs.mkdir(outputFolder)
var xmlFileName = documentData.code + "-" + creationDateLongString + ".xml";
var pdfFileName = documentData.code + "-" + creationDateLongString + ".pdf";
const pages = [];
for(var index=0;index < documentData.pages.length; ++index) {
const page = documentData.pages[index];
var data = new Buffer(page, "base64");
var dataEncoding = imageType(data).mime === "image/png" ? "png" : "jpg";
var fileName = "page" + index + "." + dataEncoding;
var targetFilePath = outputFolder + "/" + fileName
yield fs.writeFile(targetFilePath,data);
pages.push(fileName);
}
...
}
What I don't understand is why in the above code page only gets assigned once, on the first iteration, and holds that same value during the other iterations. So if I have 5 pages I end up 5 times with the data of the first page in that variable.
I am running node 4 without any special arguments or postprocessor. Simply npm run ... which maps to a node src/main/myApp.js in my package.json
I am probably missing something simple here but I've never seen this before when doing client side ES6 code. The big difference of course being that the client side code goes through Babel + Webpack and the server side code is ran directly through node.
Small addendum: if you are wondering why the "old school" for syntax and not something along the lines of pages.forEach(...., it's because this is existing code where I just did a few minor modifications.

This will work as you are expecting in strict mode. Try adding...
"use strict";
You will only see this behavior in environments (like Node) that actually respect and enforce the keyword. Babel simply converts all let and const to var right now to provide ES5 compatibility. To demonstrate, take a look at this Babel example. You can see in the output that const has been changed to var

Related

Trying to download a file in a loop with node.js, but seems to get some sync problems

I'm trying to make a simple program in node.js that will download the same file with some interval. If the downloaded file is newer than the previous, then it will be saved in a new filename with the help of a counter.
If it's a new file, then I want to save it in the last_unique.jpg name and use it to compare next time the file will be downloaded. But it doesn't seem to work. For test, I just have an empty last_unique.jpg that I would expect to be overwritten. But it never is, so every time the jpg file is downloaded, it is unique and saves it in file3.jpg, file3.jpg, etc.
However, the output also looks like maybe some async issues? It skips the first couple of times.
OUTPUT:
downloading 1
downloading 2
downloading 3
Unique file spotted!
downloading 4
Unique file spotted!
downloading 5
Unique file spotted!
downloading 6
Unique file spotted!
downloading 7
Unique file spotted!
downloading 8
Unique file spotted!
Here is the code:
const http = require('http');
const fs = require('fs');
const md5File = require('md5-file');
const fileToDownload = "http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg";
var counter = 0;
function request() {
counter = counter + 1
console.log("downloading " + counter);
const save = fs.createWriteStream("last_download.jpg");
http.get(fileToDownload, function(response) {
response.pipe(save)
});
const hash1 = md5File.sync('last_download.jpg');
const hash2 = md5File.sync('last_unique.jpg');
// it is a new file
if (hash1.localeCompare(hash2) != 0) {
console.log('Unique file spotted!');
fs.copyFileSync('last_download.jpg','last_unique.jpg');
fs.copyFileSync('last_unique.jpg','file' + counter + '.jpg');
}
}
setInterval(request, 3000);
const http = require('http');
const fs = require('fs');
const md5File = require('md5-file');
const fileToDownload = "http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg";
var counter = 0;
function request() {
counter = counter + 1;
console.log("downloading " + counter);
const save = fs.createWriteStream("last_download.jpg");
http.get(fileToDownload, function(response) {
response.pipe(save);
response.on('end',function () {
save.end();
})
});
save.on('finish',function () {
const hash1 = md5File.sync('last_download.jpg');
const hash2 = md5File.sync('last_unique.jpg');
console.log(hash1,hash2);
// it is a new file
if (hash1.localeCompare(hash2) != 0) {
console.log('Unique file spotted!');
fs.copyFileSync('last_download.jpg','last_unique.jpg');
fs.copyFileSync('last_unique.jpg','file' + counter + '.jpg');
}
});
}
setInterval(request, 3000);
You need to listen for the finish event on the stream otherwise it maybe the case that you call the copy function before the stream has completely been written. Hence a partial image is copied from the last_download.jpg to last_unique.jpg which means the hashes would be different. This is due the asynchronous nature of copying and http request.

Passing a variable between pipes in Gulp 3.9.1

Using gulp 3.9.1
I am attempting to return a bunch of files and perform a task that requires a var to be passed between two pipes.
I'm using node uuid to create a v3 UUID for each file path to
ultimately end up with a uuid for each page. I'm grabbing the file path with gulp-print.
I want to store that uuid value as a var. In the next pipe Im using
gulp-inject-string to write it into the page during the build.
Help: Either I need help getting the file path inside the gulp-inject-string pipe or I need to pass the var between the two different pipes. If I globally set a var with a default value outside the src it gets passed easily to the pipe(inject).
Super simplified code below:
// test code
var gulp = require('gulp');
var print = require('gulp-print');
var inject = require('gulp-inject-string');
var reload = browserSync.reload;
const uuidv3 = require('uuid/v3');
var uuid;
gulp.task('uuid', function() {
return gulp.src('**/*.html'])
// create uuid
.pipe(print(function(filepath) {
uuid = uuidv3(filepath, uuidv3.URL);
return "compiled: " + filepath + ' uuid: ' + uuid;
}))
// need to to add UUIDv3 to each page
.pipe(inject.before('</head>', '<meta name="dc.identifier" content="' + uuid + '">'))
.pipe(gulp.dest('/prod/./'))
.pipe(reload({ stream: true }));
});
It's worth noting that I need a cross platform way to get the file path starting in the root of the project and including forward slashes. The gulp(print) does this perfectly starting at the root of the project and ignoring anything upstream from that point. The format of the path is important because it's one half of the equation in creating the uuid and the uuid's must match on Mac or PC platforms.
examples:
/index.html
/dir1/file.html
/dir1/dir2/dir3/file.html
var gulp = require('gulp');
var print = require('gulp-print');
var inject = require('gulp-inject-string');
const uuidv3 = require('uuid/v3');
var tap = require('gulp-tap');
// you can declare here
var uuid;
gulp.task('pages', function() {
// or you can declare here
var uuid;
return gulp.src('**/*.html')
// bunch of stuff happens here involving templating/minifying
// create uuid
.pipe(print(function(filepath) {
// then set it here and use it further below
// it will be available
uuid = uuidv3(filepath, uuidv3.URL);
return "compiled: " + filepath + ' uuid: ' + uuid;
}))
// need to to add UUIDv3 to each page
//.pipe(inject.before('</head>', '<meta name="dc.identifier" content="' + uuid + '">\n'))
.pipe(tap(function(file, t) {
return t.through(inject.before('</head>', '<meta name="dc.identifier" content="' + uuid + '">\n');
})
.pipe(gulp.dest('/prod/./'))
.pipe(reload({stream:true}));
});
You are just creating a variable at a higher scope that you can set and refer to later. If you need a bunch of them create an array with filepath as an index. But I would try it first as just a simple value.
I solved the problem. It was an amateur mistake. I returned the statement where the var was set so the var was essentially killed. Updated code that allows the var to pass through the pipes.
var gulp = require('gulp');
var print = require('gulp-print');
var replace = require('gulp-replace');
const uuidv3 = require('uuid/v3');
var uuid;
gulp.task('build', function() {
return gulp.src('**/*.html')
// get a cross-platform filepath and create a uuid
.pipe(print(function(filepath) {
uuid = uuidv3(filepath, uuidv3.URL);
}))
// inject uuid
.pipe(replace('dc.identifier" content=""', function() {
return 'dc.identifier" content="' + uuid + '"';
}))
.pipe(gulp.dest('/prod/./'));
});
The var uuid passes through the pipes just fine now. This code creates a UUID based on a cross-platform file path and injects it into an empty dc.identifier meta tag.

Node.js file/folder auto completion on user input

I'm creating a cmd program in Node.js that receives user input and one of those inputs is a folder.
Now I want to make it easier for the user to choose a folder (like the cmd autocompletion for files when using commands such as 'cd'), rather than actually type the whole path.
Is there any best practice for doing that?
Thanks in advance!
I saw someone implemented that in cofmon before. So these links can helpful:
https://github.com/rbrcurtis/cofmon/blob/master/cofmon.coffee#L6
https://nodejs.org/api/readline.html#readline_use_of_the_completer_function
https://stackoverflow.com/a/31434172/883571
https://gist.github.com/DTrejo/901104
Ok, so after looking at what jiyinyiyong answered I was able to get what I wanted.
Basically this is it:
var readline = require('readline');
var fs = require('fs');
function completer(line) {
var currAddedDir = (line.indexOf('/') != - 1) ? line.substring(0, line.lastIndexOf('/') + 1) : '';
var currAddingDir = line.substr(line.lastIndexOf('/') + 1);
var path = __dirname + '/' + currAddedDir;
var completions = fs.readdirSync(path);
var hits = completions.filter(function(c) { return c.indexOf(currAddingDir) === 0});
var strike = [];
if (hits.length === 1) strike.push(currAddedPath + hits[0] + '/');
return (strike.length) ? [strike, line] : [hits.length ? hits : completions, line];
}
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
completer: completer
});
rl.question('whatever', function(answer) {
// Do what ever
});

gruntjs 0.4 registerHelper deprecated, syntax fix?

I've been running the following task for grunt 0.3 and moved to 0.4 and registerHelper has been deprecated. What is the correct method of implementing this with the new API.
module.exports = function(grunt) {
'use strict';
var fs = require('fs');
var path = require('path');
var crypto = require('crypto');
grunt.registerTask('wpversion', 'Set the versions in scripts.php for CSS/JS', function() {
var scriptsPhp = 'src/lib/include_functions/scripts.php';
// Hash the CSS
var hashCss = grunt.helper('md5', 'dist/assets/css/main.min.css');
// Hash the JS
var hashJs = grunt.helper('md5', 'dist/assets/scripts/scripts.min.js');
// Update scripts.php to reference the new versions
var regexCss = /(wp_enqueue_style\('main_css',(\s*[^,]+,){2})\s*[^\)]+\);/;
var regexJs = /(wp_register_script\('main_js',(\s*[^,]+,){2})\s*[^,]+,\s*([^\)]+)\);/;
var content = grunt.file.read(scriptsPhp);
content = content.replace(regexCss, "\$1 '" + hashCss + "');");
content = content.replace(regexJs, "\$1 '" + hashJs + "', " + "\$3);");
grunt.file.write(scriptsPhp, content);
grunt.log.writeln('"' + scriptsPhp + '" updated with new CSS/JS versions.');
});
/**
* The 'md5' helper is a basic wrapper around crypto.createHash
*/
grunt.registerHelper('md5', function(filepath) {
var hash = crypto.createHash('md5');
hash.update(fs.readFileSync(filepath));
grunt.log.write('Versioning ' + filepath + '...').ok();
return hash.digest('hex');
});
};
Just make it a normal function. If it's large put it in an separate file and require it. If it's reusable for other tasks, publish it as a node module so that other tasks can make use of it.
The grunt-helpers are deprecated in favor of node's require.
There is a project to help use the legacy helpers:
https://github.com/gruntjs/grunt-lib-legacyhelpers

Cannot find module when attempting to automatically import node.js modules

I have a directory tmp that has 3 test node.js modules [mod0.js, mod1.js, mod2.js].
I am attempting to write a function in order to import these three modules into an array and then return said array. I can drop to REPL and can import each file using var x = require("./tmp/mod0"); etc without any issue.
When I attempt to run the following function though to automate this, I receive the error [Error: Cannot fine module './tmp/mod0'].
var _importFiles = function(path, files){
var moduleList = []
, trimmedName;
files.forEach(function (element, index, array){
if (_fs.lstatSync(path + "/" + element).isFile()){
trimmedName = element.substring(0, (element.length - 3));
moduleList.push(require("./" + path + "/" + trimmedName));
}
});
return moduleList;
};
I am passing in 'tmp' for the path parameter and the output of fs.readdirSync(path) for the files parameter.
If I check process.cwd(); within the if block, it matches that of the REPL console.
I'm trying to figure out why it works when I manually do it in REPL but not automated.
I modified the code slightly to this:
var _fs = require('fs');
var path = process.cwd() + '/tmp'
var _importFiles = function(path, files){
var moduleList = [], trimmedName;
files.forEach(function (element, index, array){
if (_fs.lstatSync(path + "/" + element).isFile()){
trimmedName = element.substring(0, (element.length - 3));
moduleList.push(require("./" + path + "/" + trimmedName));
}
});
return moduleList;
};
var imports = _importFiles('./tmp', _fs.readdirSync(path));
console.log(imports);
Which gives me:
$ node import.js
[ 'imported mod0 automatically', 'imported mod1 automatically' ]
The mod files are simple module.exports = "imported mod(x) automatically";
So now my return list has an array. Also; Make sure your directory has read permissions (which im sure it does)

Resources