Encountering an async issue with npm exiftool in JavaScript

I'm facing issues with npm exiftool usage. (https://www.npmjs.com/package/exiftool) I'm attempting to perform some tasks using it.

  1. Iterate through image files in a specific folder
  2. Retrieve 'xpKeywords' data of each image file
  3. Write the file storing data

Below is my code snippet.

const fs = require('fs');
const exif = require('exiftool');
const folderName = 'testImages';
const inputPath = `/Users/myName/Project/project/${folderName}`;
const files = fs.readdirSync(inputPath, 'utf8');

let data = [];

(async () => {
  let promises = [];
  files.forEach(file => promises.push(fs.readFileSync(`${inputPath}/${file}`)));
  let results = await Promise.all(promises);
  for(let [index, result] of results.entries()) {
    let datum = await getMetadata(result, index);
    console.log("out");
    data.push(datum);
  }
  fs.writeFileSync('outputData/metaData.json', JSON.stringify(data, null, 4), (error) => {
    console.log('Error Occurred');
  });
})();

async function getMetadata(result, index) {
  console.log(`get metadata ${index}`);
  await exif.metadata(result, (err, metadata) => {
    return {
      name: files[index],
      hashTags: metadata.xpKeywords
    };
  });
}

Upon executing this code, the generated metaData.json file does not match my expectations.

[ null, null, null, null, null, null ]

I suspect there might be an issue with the use of async functions within the getMetadata function.

Answer №1

There seems to be a few issues in your code.

  • The main problem is that the function getMetaData does not return any value. Simply returning from the callback passed to exif.metadata does not set the return value of getMetaData.
  • Additionally, fs.readFileSync does not return a promise, so using
    let results = await Promise.all(promises);
    serves no purpose as it operates synchronously.
  • Accessing files via indexes in a module global variable can lead to errors.

To handle promises effectively, consider utilizing Node.js's experimental file promises API, or apply util.promisify on fs.readFile for asynchronous functionality. You can also use promisify on exif.metadata to work with promises.

For detailed information on converting callback-based APIs to promises, refer to this question's answers.

In light of these considerations, here's a modified version of your code (may require adjustments):

const fs = require('fs');
const {promisify} = require('util');
const exif = require('exiftool');

const pReaddir = promisify(fs.readdir);
const pReadFile = promisify(fs.readFile);
const pWriteFile = promisify(fs.writeFile);
const pMetadata = promisify(exif.metadata);

const folderName = 'testImages';
const inputPath = `/Users/myName/Project/project/${folderName}`;

(async () => {
    // Retrieve file names
    const files = await pReaddir(inputPath, 'utf8');
    // Process files concurrently and wait for the result
    const data = Promise.all(files.map(async(name) => {
        // Read the file and extract metadata
        let {xpKeywords: hashTags} = await pMetadata(await pReadFile(`${inputPath}/${name}`));
        // Construct an object with the filename and xpKeywords as hashTags
        return {name, hashTags};
    }));
    // Save the results to a file
    await pWriteFile('outputData/metaData.json', JSON.stringify(data, null, 4));
})()
.catch(error => {
    console.error(error);
});

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Add information to the Database seamlessly without the need to refresh the page using PHP in combination with JQuery

Check out my code below: <form action='insert.php' method='post' id='myform'> <input type='hidden' name='tmdb_id'/> <button id='insert'>Insert</button> <p i ...

React component using Highcharts is displaying categories as numeric values instead of text

Incorporating the highcharts-react package (https://github.com/highcharts/highcharts-react) into my Laravel project has presented a unique challenge. I am attempting to fetch data from my reducer and transform it into a string to populate the categories in ...

"The onkeydown event dynamically not triggering for children elements within a parent element that is contentEditable

Can anyone offer some insights into why this code isn't functioning as expected? My goal is to attach the listener to the children elements instead of the body so that I can later disable specific keystrokes, such as the return key. <!DOCTYPE html ...

JavaScript throws a "null is null or not an object" error when attempting to

I am dealing with a div that contains flash content in the form of Open Flash Chart. However, I encounter a javascript error when trying to remove child divs using $("#mydiv").children().remove() specifically in IE 8: The error message reads: "null is n ...

Struggling to retrieve the tagName attribute when clicking in JavaScript

I am trying to extract the tagName of any element within an iframe when it is clicked. Unfortunately, my JavaScript code is not working as expected. As a beginner in JavaScript, I would appreciate some guidance on where I might be going wrong. <div cl ...

The function `Object.entries().map()` in TypeScript does not retain the original data types. What changes can I make to my interface to ensure it works correctly, or is there a workaround

Working with this interface: export interface NPMPackage { name: string; description: string; 'dist-tags': { [tag: string]: string; }; versions: { [version: string]: { name: string; version: string; dependencie ...

Authentication - The success callback of $http is executed rather than the error callback

I seem to be facing an issue with authentication in a MEAN stack app, possibly due to my limited understanding of promises and the $http's .then() method. When I try to authenticate to my backend Node server with incorrect credentials, the success cal ...

CSS or jQuery: Which is Better for Hiding/Showing a Div Within Another Div?

Show only class-A at the top of the page while hiding all other classes (x,x,c). Hide only class-A while showing all other classes (x,x,c). Is it possible to achieve this? <div class="x"> <div class="y"> <div class="z"&g ...

Node-sass installation through NPM causing errors

I'm encountering difficulties installing node-sass in a project that builds and runs perfectly on my computer, but is causing major issues on my Surface when trying to install the packages. Note: I've attempted reinstalling and rebuilding the pr ...

Using placeholder in number and range input in Angular.js to display a placeholder text instead of a value when the input is 0

I am using Angular.js to link a number and range input so they work together with the same value. I have set the default value to 0 when the page loads as $scope.lbs_needed = 0;. My goal is to display a placeholder="0" whenever the user sets the value of e ...

Challenges Encountered with Random Image Generator in JavaScript

I am encountering an issue with a script that is supposed to change the default images to a random image each time the page is refreshed. However, I keep receiving an error stating that boxID is null. Why is boxID null? <script type="text/javascript" ...

Development with Node JS, express, Mongoose, and intricate nested queries

I'm struggling with a group of interconnected queries using express/mongoose, structured like this: app.get(..., function(...) { Schema1.query(..., function(..., res1) { for ( var key in res1 ) { Schema2.query(..., function(..., ...

Why does Googlebot need to retrieve HTML from JSON-exclusive URLs?

Link to a page like this: The following code is found: $.getJSON("/newsfeeds/61?order=activity&amp;type=discussion", function(response) { $(".discussion-post-stream").replaceWith($(response.newsfeed_html)); $(".stream-posts").before($("<div cl ...

Creating a simple bootstrap code for developing plugins in Wordpress

After successfully coding in Brackets with the Theseus plugin on my local machine, I encountered a problem when attempting to transfer my code to a Wordpress installation. The transition from Brackets to Wordpress seems far more complicated than expected. ...

Is it a wise decision to provide the client with a new token just one minute before the expiration of the old one?

When monitoring my backend, I constantly check the remaining time before the JWT expires, which is set to 15 minutes. If there is only one minute left or less, I generate a new JWT and include it in the response header as a setToken. The front-end can then ...

Model in Sequelize does not get updated

I have a basic user model with two simple relationships: export const Password = sequelize.define("password", { hash: { type: DataTypes.STRING, allowNull: false, }, salt: { type: DataTypes.STRING, allow ...

Removing a child element in ReactJS that was dynamically created based on a count

Currently, I'm working on a project with two React components: TrackSection (the parent element) and TrackItem (the child). The TrackSection contains a button that adds a new TrackItem every time it is clicked by incrementing the numTracks variable. W ...

What is the reasoning behind excluding any JavaScript code between the <script> tags when our intention is solely to incorporate an external JS file?

It's puzzling to me why the author of the "Javascript & Jquery: the missing manual , second edition" recommends the following sentence: When including the src attribute to connect to an external JavaScript file, avoid placing any JavaScript cod ...

Navigating a dynamic table by looping through its generated tr elements

I am currently working with a dynamically created tr table that includes individual rows of data and a fixed total sum at the bottom. The total sum does not change dynamically. var tmp = '<tr id="mytable"> <td id="warenid">'+data1.id ...

Expanding a feature that modifies the CSS properties of every input element

Creating a text tracking (letter-spacing) demonstration where range sliders are used to update CSS properties. I'm looking to improve the functionality so that the labels also update dynamically, without repeating output2.textContent = this.value;. An ...