Please excuse my noobness, but why isn't this working?
then() is never fired, and neither is error(). Promise seems to never resolve.
Any pointers appreciated. Thanks.
var Promise = require('bluebird');
var execFile = require('child_process').execFile;
execFile = Promise.promisify(execFile);
var IMAGE_DIR = "resources/assets/images";
var validImages = ['.jpg', '.png'];
... // setup omitted ...
execFile('find', [IMAGE_DIR], function (err, stdout, stderr) {
var images = [];
return new Promise(function(resolve) {
var fileList = stdout.split('\n');
images = fileList.filter(function (image) {
var ext = path.extname(image);
if (validImages.indexOf(ext) > -1) {
return image;
}
})
return resolve(images);
})
}).then(function () {
console.log(arguments);
}).catch(console.log.bind(console));
You're just not using the promisified version of execFile() properly.
You should be doing:
const Promise = require('bluebird');
const execFile = Promise.promisify(require('child_process').execFile);
execFile('find', [IMAGE_DIR]).then(function(stdout) {
// process result here
}).catch(function(err) {
// handle error here
});
If you need access to both stdout and stderr, then you have to pass the multiArgs option to .promisify().
const Promise = require('bluebird');
const execFile = Promise.promisify(require('child_process').execFile, {multiArgs: true});
execFile('find', [IMAGE_DIR]).then(function(args) {
let stdout = args[0];
let stderr = args[1];
// process result here
}).catch(function(err) {
// handle error here
});
I highly appreciate the answer given by jfriend000. Anyways, if you want a ES7 solution with async-await:
const Promise = require('bluebird');
const execFile = Promise.promisify(require('child_process').execFile
const find = async () => {
try{
let output = await execFile('find', [IMAGE_DIR]);
// handle your output with the variable
} catch(error) {
// handle your errors here
}
}
Related
I tried and have looked at StackOverflow and the other posts are not answering my questions. This is a unique question. How can I get the path of the most recently uploaded file saved to a variable so it can be used later?
Code:
var pathtocheck = "C:\Users\user1\Downloads";
var path = require('path');
var fs = require('fs');
var getMostRecent = function (dir, cb) {
var dir = path.resolve(dir);
var files = fs.readdir(dir, function (err, files) {
var sorted = files.map(function(v) {
var filepath = path.resolve(dir, v);
return {
name:v,
time:fs.statSync(filepath).mtime.getTime()
};
})
.sort(function(a, b) { return b.time - a.time; })
.map(function(v) { return v.name; });
if (sorted.length > 0) {
cb(null, sorted[0]);
} else {
cb('Y U NO have files in this dir?');
}
})
}
await getMostRecent(pathtocheck, function (err, recent) {
if (err) console.error(err);
console.log(recent);
});
var lastdownloadedimage = ;
With callbacks, you have to write your code in the callback passed to getMostRecent, so
// Note: awaiting this takes no effect unless getMostRecent returns a promise.
getMostRecent(pathtocheck, function (err, recent) {
if (err) console.error(err);
console.log(recent);
var lastdownloadedimage = recent;
// Or just directly use `recent`
});
Or,
Async-await and Promise can also solve your issue, though I'm not sure how much you're familiar with them.
You can use Promisified version of file system API in Node.js v10 or above, by require('fs').promises instead of require('fs') (Documentation here)
Decalration of the functions like this:
// Also it's a good practice to use `const` or `let`, instead of `var`
const pathtocheck = "C:\Users\user1\Downloads";
const path = require('path');
const fs = require('fs');
const fsp = require('fs').promises;
// Decalre the function with `async` to use `await` in it
const getMostRecent = async function (dir) {
dir = path.resolve(dir);
const files = await fsp.readdir(dir)
const sorted = files.map(function(v) {
const filepath = path.resolve(dir, v);
return {
name:v,
time:fs.statSync(filepath).mtime.getTime()
// maybe you can use fsPromises.stat here
};
})
.sort(function(a, b) { return b.time - a.time; })
.map(function(v) { return v.name; });
if (sorted.length > 0) {
return sorted[0];
} else {
// Now you have no callbacks, so there are two options to return the error state.
// 1. Throw an Error with an error message
// 2. Return a special value such as `null` or `false`, which you can track it.
}
}; // <-- perhaps you need place a semicolon here.
And you call the function in async IIFE, wrapping anonymous async function to use await
(async function() {
const lastdownloadedimage = await getMostRecent(pathtocheck);
console.log(lastdownloadedimage)
})();
Or use Promise.then:
getMostRecent(pathtocheck).then(function(recent) {
var lastdownloadedimage = recent;
// Again, you can just directly use `recent`
})
I would like to get the pixels from some images and return them as an array. For the image handling I use https://www.npmjs.com/package/jimp . Jimp has an asynchronous function jimp.read(filePath) that needs to get handled with await. My image reader module:
const config = require('./configuration.json');
const fs = require('fs');
const path = require('path');
const jimp = require('jimp');
module.exports = readImages;
function readImages() { // Reads the image files and extracts the colors
const files = getFilesFromDirectory();
const imageFiles = filterForImageFiles(files);
return getInformationFromImageFiles(imageFiles);
}
function getFilesFromDirectory() { // Reads all the files from the directory provided from the configuration file
return fs.readdirSync(config.dirPath);
}
function filterForImageFiles(files) { // Filters an array of files for .png and .jpg files
return files.filter(file => {
const fileExtension = path.extname(file);
const isPngFile = fileExtension === '.jpg';
const isJpgFile = fileExtension === '.png';
return isPngFile || isJpgFile;
});
}
function getInformationFromImageFiles(imageFiles) { // Maps image files to image information
return imageFiles.map(imageFile => getInformationFromImageFile(imageFile));
}
async function getInformationFromImageFile(imageFile) { // Extracts information from an image file
const filePath = path.join(config.dirPath, imageFile);
const image = await jimp.read(filePath);
return getColorsFromImage(image);
}
function getColorsFromImage(image) { // Extracts the colors from an image file
const { width, height } = image.bitmap;
const colors = [,];
for (let x = 0; x < width; x++) {
for (let y = 0; y < height; y++) {
const intColor = image.getPixelColor(x, y);
const rgbaColor = jimp.intToRGBA(intColor);
colors[x, y] = rgbaColor;
}
}
return colors;
}
When I run the code I receive an array with two items (because I provided two images). Both are Promise { <pending> }. Please have a look at getInformationFromImageFile which is an async function awaiting the jimp reader.
Why does it return a promise and does not resolve it? Do I have to await every function and the whole module ... ?
As getInformationFromImageFile is marked async it will return a Promise therefore it must be awaited. You need to await where it is called. These changes should fix it:
async function getInformationFromImageFiles(imageFiles) {
const imageInfos = [];
for (let i = 0; i < imageFiles.length; i++) {
const imageFile = imageFiles[i];
imageInfos.push(await getInformationFromImageFile(imageFile));
}
return imageInfos;
}
async function readImages() {
const files = getFilesFromDirectory();
const imageFiles = filterForImageFiles(files);
return await getInformationFromImageFiles(imageFiles);
}
You also need to await the function getInformationFromImageFile().
async/await is just like promises. You gotta handle your async returns like you handle promises.
Whenever you invoke an async function, you gotta await it in another async function or use .then() like you do with promises.
// make this function async
async function readImages() {
// Reads the image files and extracts the colors
const files = getFilesFromDirectory();
const imageFiles = filterForImageFiles(files);
// the next line is an async call - so await it
const images = await getInformationFromImageFiles(imageFiles); // array of images
return images;
}
function getFilesFromDirectory() {
// Reads all the files from the directory provided from the configuration file
return fs.readdirSync(config.dirPath);
}
function filterForImageFiles(files) {
// Filters an array of files for .png and .jpg files
return files.filter((file) => {
const fileExtension = path.extname(file);
const isPngFile = fileExtension === '.jpg';
const isJpgFile = fileExtension === '.png';
return isPngFile || isJpgFile;
});
}
// make this function async
async function getInformationFromImageFiles(imageFiles) {
// promisify all async returns
return Promise.all(imageFiles.map((imageFile) => getInformationFromImageFile(imageFile)));
}
// return async
async function getInformationFromImageFile(imageFile) {
// Extracts information from an image file
const filePath = path.join(config.dirPath, imageFile);
const image = await jimp.read(filePath);
return getColorsFromImage(image);
}
Async/Await always returns Promise, so you can do something like this:
Promise
.all(readImages())
.then(imd => console.log(imd))
.catch(error => console.log(error));`
As other member said, your function needs to be awaited hence it will return the promise result.
If you want to avoid awaiting the function, you can get your promise result in a synchronous way, like this:
let x = new Promise(function(){
//code
});
x.then(function(data){
//Promise resolved, do something
}).then(function(err){
//Promise rejected, do something
});
I created a node application which does is that it scraps google and downloads top 15 images and then store it in HDD in a folder which is the query received after compressing. Now problem that I'm facing is When I'm going back to read that folder using readdirSync and storing the results in error, it returns an empty array, what is wrong with the code.
request(url, function (error, response, body) {
if (!error) {
var $ = cheerio.load(body);
var imgNodes = $('#ires td a img');
// imgNodes is merely an array-like object, sigh.
// This is purposedly old-school JS because newer stuff doesn't work:
var urls = [];
for(let i = 0; i <= 14; i++){
let imgNode = imgNodes[i];
urls.push(imgNode.attribs['src']);
}
// console.log(urls);
const processCompress = new Promise(resolve => {
fs.mkdir(path.join(__dirname,'Photos',query), function (error) {
let j = 0;
if(!error){
for(i in urls){
console.log(i);
var source = tinify.fromUrl(urls[i]);
source.toFile(path.join(__dirname,'Photos', query,"optimized_"+ ++j +".jpg"));
}
}});
resolve();
});
const getFiles = new Promise(resolve => {
fs.readdirSync(path.join(__dirname,'Photos', query)).forEach(function (file) {
fileName.push(path.join(__dirname,'Photos',query,file));
});
resolve();
});
function colourMeBw(){
for(let k = 0; k < fileName.length; k++){
Jimp.read(fileName[k], (err, image) => {
if (err) throw err;
image.greyscale().write(fileName[k]);
});
}}
processCompress.then(() => getFiles);
colourMeBw();
} else {
console.log("We’ve encountered an error: " + error);
}
There are a number of things wrong with your code:
In processCompress(), you are resolving the promise before fs.mkdir() is done and before any of the images have been fetched and written.
In getFiles() you are wrapping a synchronous I/O function in a promise. The first problem is that you shouldn't be using synchronous I/O at all. That is the fastest way to wreck the scalability of your server. Then, once you switch to the async version of fs.readdir(), you have to resolve the promise appropriately.
There's no way to know when colourMeBw() is actually done.
You should never iterate an array with for(i in urls) for a variety of reasons. In ES6, you can use for (url of urls). In ES5, you can use either a traditional for (var i = 0; i < urls.length; i++) {} or urls.forEach().
You have no error propagation. The whole process would choke if you got an error in the middle somewhere because later parts of the process would still continue to try to do their work even though things have already failed. There's no way for the caller to know what errors happened.
There's no way to know when everything is done.
Here's a version of your code that uses promises to properly sequence things, propagate all errors appropriately and tell you when everything is done. I don't myself know the tinify and Jimp libraries so I consulted their documentation to see how to use them with promises (both appear to have promise support built-in). I used the Bluebird promise library to give me promise support for the fs library and to take advantage of Promise.map() which is convenient here.
If you didn't want to use the Bluebird promise library, you could promisify the fs module other ways or event promisify individual fs methods you want to use with promises. But once you get used to doing async programming with promises, you're going to want to use it for all your fs work.
This is obviously untested (no way to run this here), but hopefully you get the general idea for what we're trying to do.
const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));
const Jimp = require('jimp');
const rp = require('request-promise');
rp(url).then(function(body) {
var $ = cheerio.load(body);
var imgNodes = $('#ires td a img');
// imgNodes is merely an array-like object, sigh.
// This is purposedly old-school JS because newer stuff doesn't work:
var urls = [];
for (let i = 0; i <= 14; i++) {
let imgNode = imgNodes[i];
urls.push(imgNode.attribs['src']);
}
// console.log(urls);
const processCompress = function() {
return fs.mkdirAsync(path.join(__dirname, 'Photos', query).then(function(error) {
let j = 0;
return Promise.map(urls, function(url) {
var source = tinify.fromUrl(url);
return source.toFile(path.join(__dirname, 'Photos', query, "optimized_" + ++j + ".jpg"));
});
});
});
const getFiles = function() {
return fs.readdirAsync(path.join(__dirname, 'Photos', query).then(function(files) {
return files.map(function(file) {
return path.join(__dirname, 'Photos', query, file);
});
});
};
function colourMeBw(fileList) {
return Promise.map(fileList, function(file) {
return Jimp.read(file).greyscale().write(file);
});
}
return processCompress().then(getFiles).then(colourMeBw);
}).then(function() {
// all done here
}).catch(function(err) {
// error here
});
Your query variable in use here does not appear to be defined anywhere so I am assuming it is defined in a higher scope.
Note that one big advantage of using promises for a multi-stage operation like this is that all errors end up on one single place, no matter where they occurred in the overall multi-level process.
Note: If you are processing a large number of images or a medium number of large images, this could end up using a fair amount of memory because this code processes all the images in parallel. One of the advantages of Bluebird's Promise.map() is that is has an optional concurrency option that specifies how many of the requests should be "in-flight" at once. You can dial that down to a medium number to control memory usage if necessary.
Or, you could change the structure so that rather than compress all, then convert all to greyscale, you could compress one, convert it grey scale, then move on to the next, etc...
I read the code and I think what you are trying to do is something like this:
const cheerio = require("cheerio");
const fetch = require("node-fetch");
const tinify = require("tinify");
const fs = require("fs");
const path = require("path");
const getImages = url => {
return fetch(url)
.then(responseToText)
.then(bodyToImageUrls)
.then(makePhotoDirectory)
.then(downloadFiles)
.then(processImageData)
.then(doImageManipulation)
.catch(error => {
console.log("We’ve encountered an error: " + error);
});
};
const responseToText = res => res.text();
const bodyToImageUrls = body => {
const $ = cheerio.load(body);
return $("img").attr("src");
};
const imgNodesToUrls = imgNodes => {
return imgNodes.map(imgNode => imgNode.name);
};
const makePhotoDirectory = urls => {
const dir = path.join(__dirname, "Photos");
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
return urls;
};
const downloadFiles = urls => {
/*
I could not run this since I don't have a Tinify API key
but I assume that it returns a promise.
*/
const promises = urls.map(url => tinify.fromUrl(url));
return Promise.all(promises);
};
const processImageData = imageData => {
const promises = imageData.map((data, i) => {
const fileUrl = path.join(__dirname, "Photos", `optimized_${i}.jpg`);
return data.toFile(fileUrl);
});
return Promise.all(promises);
};
const doImageManipulation = images => {
// Add your image manipulation here
};
I thought that the resolve() call directly after fs.mkdir is wrong, because mkdir is working asnyc, so the resolve is reached without performing the whole work of mkdir.
const processCompress = new Promise(resolve => {
fs.mkdir(path.join(__dirname, 'Photos', query), function(error) {
let j = 0;
if (!error) {
for (i in urls) {
console.log(i);
var source = tinify.fromUrl(urls[i]);
source.toFile(path.join(__dirname, 'Photos', query, "optimized_" + ++j + ".jpg"));
}
}
resolve(); // <---- inside callback from mkdir.
});
// call the resolve from inside the mkDirs-callback function
// resolve();
});
I hope that will fix your problem.
I use the following code which is working OK
var ncp = require('ncp').ncp;
function load(folderPath) {
ncp.limit = 16;
var path = require('path');
var localPath = path.join(__dirname, '../pl');
ncp(folderPath, localPath, {dereference: false}, function (err) {
if (err) {
return console.error(err);
}
console.log('done to save the files!');
});
};
I want to use promise instead of callback but when using the following I got error
var Promise = require('bluebird');
var ncp = Promise.promisifyAll(require('ncp').ncp);
function load(folderPath) {
ncp.limit = 16;
var localPath = path.join(__dirname, '../pl');
ncp(folderPath, localPath, {dereference: false})
.then(function (result) {
console.log('done to save the files!');
})
.catch(function (err) {
console.err(err);
});
};
The error is :
TypeError: Cannot read property 'then' of undefined
Promise.promisifyAll() is used to promisify objects. It will iterate the object and promisify all of the function properties on that object.
However, ncp is a function itself, it doesn't have properties, so promisifyAll() won't do. You're looking for promisify() which takes a function as an argument, and returns a promisified function.
So all you need to do is change
var ncp = Promise.promisifyAll(require('ncp').ncp);
to
var ncp = Promise.promisify(require('ncp').ncp);
I've used the following code to call two modules, but the invoke action is called before the validate file (I saw in debug). What I should do to verify that validateFile is called before appHandler.invokeAction? Should I use a promise?
var validator = require('../uti/valid').validateFile();
var appHandler = require('../contr/Handler');
appHandler.invokeAction(req, res);
Update
this is the validate file code
var called = false;
var glob = require('glob'),
fs = require('fs');
module.exports = {
validateFile: function () {
glob("myfolder/*.json", function (err, files) {
var stack = [];
files.forEach(function (file) {
fs.readFile(file, 'utf8', function (err, data) { // Read each file
if (err) {
console.log("cannot read the file", err);
}
var obj = JSON.parse(data);
obj.action.forEach(function (crud) {
for (var k in crud) {
if (_inArray(crud[k].path, stack)) {
console.log("duplicate founded!" + crud[k].path);
break;
}
stack.push(crud[k].path);
}
})
});
});
});
}
};
Because glob and fs.readFile are async functions and appHandler.invokeAction is invoked during i/o from disk.
Promise is a good solution to solve this but an old school callback could do the job.
validator.validateFile().then(function() {
appHandler.invokeAction(req, res);
});
and for validate
var Promise = require("bluebird"), // not required if you are using iojs or running node with `--harmony`
glob = require('mz/glob'),
fs = require('mz/fs');
module.exports = {
validateFile: function () {
return glob("myfolder/*.json").then(function(files) {
return Promise.all(files.map(function(file) {
// will return an array of promises, if any of them
// is rejected, validateFile promise will be rejected
return fs.readFile(file).then(function (content) {
// throw new Error(''); if content is not valid
});
}));
})
}
};
If you want working with promise mz could help :)
As the fs.fileRead is async, you should put the code that you want to execute after validateFile to its callback.
The origin could be:
var validator = require('../uti/valid').validateFile();
var appHandler = require('../contr/Handler');
// create a new function that when execute, will call appHandler.invokeAction with req and res given to its arguments.
validator.validateFile(appHandler.invokeAction.bind(null, req, res));
The validator part should be:
var called = false;
var glob = require('glob'),
fs = require('fs');
module.exports = {
validateFile: function (callback) {
glob("myfolder/*.json", function (err, files) {
var stack = [];
// Use it to decide whether all files processed or not.
var filesToLoad = files.length;
files.forEach(function (file) {
fs.readFile(file, 'utf8', function (err, data) { // Read each file
--filesToLoad;
if (err) {
console.log("cannot read the file", err);
// If the invoke action doesn't depend on the result. You may want to call it here too.
}
var obj = JSON.parse(data);
obj.action.forEach(function (crud) {
for (var k in crud) {
if (_inArray(crud[k].path, stack)) {
console.log("duplicate founded!" + crud[k].path);
break;
}
stack.push(crud[k].path);
}
})
// Only called the callback after all files processed.
if (filesToLoad === 0) {
callback();
}
});
});
});
}
};
Edit: Thanks for Bergi's remind that there's the files are an array and you have to call the callback when all files is processed, so we have to further use a variable to decide how many files are not processed yet.