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
});
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 am currently working with destructuring arrays in Javascript, I would like to access these variables in other functions but currently, I am struggling to figure out how I might go about this.
I've tried calling the function and then console.log(thermostatArray) -> I believe this returned pending promise
I've tried calling the function and awaiting it and then console.log thermostatArray.
dataFormat() is properly able to see log and use the array but heatCallCheck() is not and I am not seeing past the issue yet.
var express = require("express");
var router = express.Router();
const multer = require("multer");
var Excel = require("exceljs");
const index = require("../routes/index");
const workbook = new Excel.Workbook();
async function convertFile(workbook) {
csvWorkbook = workbook.csv.readFile("./uploads/uploadedFile.csv");
await csvWorkbook.then(async function(csvWorkbook) {
const worksheet = workbook.getWorksheet("sheet1");
try {
// await dataFormat(worksheet);
await heatCallCheck(worksheet,)
} catch (err) {
console.log(err);
}
await workbook.xlsx.writeFile("./uploads/convertedFile.xlsx").then(() => {
console.log("converted file written");
});
});
}
async function dataFormat(worksheet) {
let thermostatArray = []
await csvWorkbook.then(async function(worksheet) {
const serialNum = worksheet.getCell("D1").value;
const thermostatName = worksheet.getCell("D2").value;
const startDate = worksheet.getCell("D3").value;
const endDate = worksheet.getCell("D4").value;
const thermostat = worksheet.eachRow({includeEmpty: true}, function(row,rowNumber){
if (rowNumber > 6) {
thermostatArray.push(row.values)
}
})
console.log(`${thermostatArray[5]} Array Sample from dataFormat` )
console.log(`${thermostatArray[6]} Array Sample from dataFormat` )
return thermostatArray
})}
async function heatCallCheck(worksheet,thermostatArray) {
let test = await dataFormat(worksheet).then(thermostatArray => {
return thermostatArray[5]
}).catch(err => {
console.error(err)
})
console.log(`${test} result `)
}
My expected results, in this case, would be that I would be able to see the 4th element in thermostat array using the heatCallCheck() function.
I figured I would be able to access it after the .then is called.
my understanding is that .then(thermostatArray =>
makes that array the return value.
You do this:
async function someFunction() {
const myResultFromAPromise = await functionThatReturnsAPromise();
// ....do some stuff
return myResultFromAPromise;
}
OR
function someFunction() {
return functionThatReturnsAPromise().then(function(myResultFromAPromise) {
// ...do some stuff
return myResultFromAPromise;
});
}
but don't do both, it's just terribly confusing.
EDIT: as a commenter pointed out, you can await anything, but it's clear from your code that you're very confused about the point of async/await
I am trying to learn to use properly async await but I am kind of cofused about it.
In the snippets, I am trying to build an array of object containing the infos I need about the file I am uploading in the component. The problem is that the objects in this.fileInfo are not exactly waiting the promise to return the encoded images, returning this output as I console.log this.fileInfo:
As you can see, the key image is a ZoneAwarePromise whose value is undefined. Can you please help me to fix this?
Function build()
async build(e) {
let files = e.target.files;
this.j = Array.from(files);
this.fileInfo = await this.j.reduce((acc, cur) => [
...acc, {
name: cur.name.replace(/^.*\\/, ""),
sizeunit: this.getSize(cur.size),
extention: cur.name.split(/\.(?=[^\.]+$)/).slice(-1).pop().toString(),
image: this.previewFile(cur)
}
], [])
console.log(await this.fileInfo);
}
Promise
async previewFile(file) {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
return new Promise((res) => {
res(reader.result)
}).then( res => res);
};
}
You are not awaiting anything in this function: async previewFile(file).
Perhaps you assume returning a new Promise somewhere along the lines of your code will make it function as a Promise. In this particular case it will not work, because it is inside a delegate (onload), that will not be executed within the scope of your function previewFile().
You can lose the async modifier, because you can return a Promise instead:
previewFileAsync(file) {
// the async modifier keyword is not necessary,
// because we don't need to await anything.
return new Promise((res) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => res(reader.result);
});
}
When you call this, you can await it inside your loop:
async buildAsync(e) {
let files = e.target.files;
for(let i = 0; i < files.length; i++) {
const file = files[i];
const preview = await previewFileAsync(file);
// Do something with preview here...
}
}
Of course, you can execute a range of promises to allow for some sort of concurrency, but this will help get you started.
I added the Async suffix to your method so a caller knows that this can be awaited. It does not do anything special, but it helps clarify your code. You can use whatever suffix you think is right. I'm just used to the Async suffix.
Edit
Stackblitz example of async logic
I want to return the files of a directory.
I need it to pass the route to another function.
In other words, how can I return the files of a directory using JavaScript/Node.js?
const fs = require('fs');
const path = require('path');
const mdLinks = require('../index');
exports.extension = (route) => {
return new Promise((resolve, reject) => {
try {
recursive(route);
} catch (e) {
reject(e);
}
});
}
const recursive = (route) => {
const extMd = ".md";
let extName = path.extname(route);
let files = [];
fs.stat(route, (err, stats) => {
if (stats && stats.isDirectory()) {
fs.readdir(route, (err, files) => {
files.forEach(file => {
let reFile = path.join(route, file);
if (file !== '.git') {
recursive(reFile);
}
});
})
}
else if (stats.isFile() && (extMd == extName)) {
files.push(route);
}
})
return files;
}
There are multiple problems.
First off, your function is asynchronous so it cannot just return the files value because your function returns long before anything is added to the files array (that's one reason that it's empty). It will have to either call a callback when its done or return a promise that the caller can use. You will have to fix that in both the top level and when you call yourself recursively.
Second, you are redefining files at each recursive step so you have no way to collect them all. You can either pass in the array to add to or you can define the files array at a higher level where everyone refers to the same one or you can have the call to recursive concat the files that are returned to your current array.
Third, you haven't implemented any error handling on any of your asynchronous file I/O calls.
Here's my recommended way of solving all these issue:
exports.extension = (route) => {
return recursive(route);
}
const util = require('util');
const stat = util.promisify(fs.stat);
const readdir = util.promisify(fs.readdir);
// returns a promise that resolves to an array of files
async function recursive(route) {
const extMd = ".md";
let extName = path.extname(route);
let files = [];
let stats = await stat(route);
if (stats.isDirectory()) {
let dirList = await readdir(route);
for (const file of dirList) {
if (file !== '.git') {
let reFile = path.join(route, file);
let newFiles = await recursive(reFile);
// add files onto the end of our list
files.push(...newFiles);
}
}
} else if (stats.isFile() && extMd === extName) {
files.push(route);
}
// make the files array be the resolved value
return files;
});
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.