I have been struggling with this for a few hours now. What I am essentially doing is move files based on their names, in link with a SQLite database. This is in an Electron application where I use IPC. When the renderer sends a message to the main process to move files, it then listens for an answer before displaying it in the application.
I have here very naively incremented a counter in multiple places. What I wish to do is count how many files will be moved or have been moved so that I can inform the user. The problem is that no matter what I try (Promises, async, await), the counter is never correct when it is sent. It is always 0 because the message is sent before it's incremented.
In short, what I need to do is wait for the counter to be fully incremented before sending a message. For that, the logical thing would be to wait for dir.files() or files.forEach() to be done but I haven't been able to get any of that to work. A note: printing the counter in fs.access() right after the incrementation, the values are correct (1, 2, 3, ...), printing it outside of that asynchronous call always prints 0.
Here is the full code:
ipcMain.on("moveFiles", (event, moveTo, moveFrom, databaseFile, doOverwrite, doRecursive, subFolder, doTestRun) => {
let filesMoved = 0;
const db = new sqlite3.Database(databaseFile, sqlite3.OPEN_READONLY, (cErr) => {
if (cErr) throw cErr;
let query = `...`;
// Match files based on their name
let regex = /.../;
// Get data from the database in one query
db.all(query, [], (qErr, rows) => {
if (qErr) throw qErr;
// Assume this is filled like this from the query result:
// dbNameToFolder["imgur"] = {"type": "picture", "local_folder: "Pictures"}
let dbNameToFolder = {};
// List all files (recursively or not, based on parameter)
dir.files(moveFrom, "file", function(error, files) {
if (error) throw error;
// For each file, check if it should be moved
files.forEach(function(file) {
let filename = path.basename(file);
// Run the filename against the regex
let matches = regex.exec(filename);
// If the filename matched
if (matches !== null && matches.length > 1 && dbNameToFolder.hasOwnProperty(matches[2])) {
let newPath = path.join(moveTo, dbNameToFolder[matches[2]]["type"], dbNameToFolder[matches[2]]["local_folder"], subFolder, filename);
// If this a test run, simply inform the user of which files will be moved
if (doTestRun) {
// If the user didn't ask for files to be overwritten
if (!doOverwrite) {
// Check if there already is a file at the newly defined path
fs.access(newPath, fs.F_OK, (aErr) => {
// If aErr, target file doesn't exist, we can move it freely
if (aErr) {
console.log("Will move ", file, " to ", newPath);
filesMoved++;
} else {
console.log("Moving ", file, " would overwrite ", newPath);
}
});
} else {
console.log("Will move ", file, " to ", newPath);
filesMoved++;
}
} else {
// Move files asynchronously
(async () => {
await moveFile(file, newPath, {
overwrite: doOverwrite
});
console.log(file, " has been moved to ", newPath);
filesMoved++;
})();
}
}
});
}, {
recursive: doRecursive
});
});
});
db.close();
mainWindow.webContents.send("moveFilesResult", filesMoved);
});
And here is an attempt I made.
ipcMain.on("moveFiles", (event, moveTo, moveFrom, databaseFile, doOverwrite, doRecursive, subFolder, doTestRun) => {
const db = new sqlite3.Database(databaseFile, sqlite3.OPEN_READONLY, (cErr) => {
db.all(query, [], async (qErr, rows) => {
const run = () => {
return new Promise(resolve => {
let filesMoved = 0;
dir.files(moveFrom, "file", function (error, files) {
files.forEach(function (file) {
let filename = path.basename(file);
let matches = regex.exec(filename);
if (matches !== null && matches.length > 1 && dbNameToFolder.hasOwnProperty(matches[2])) {
let newPath = path.join(moveTo, dbNameToFolder[matches[2]]["type"], dbNameToFolder[matches[2]]["local_folder"], subFolder, filename);
if (doTestRun) {
if (!doOverwrite) {
fs.access(newPath, fs.F_OK, (aErr) => {
if (aErr) {
console.log("Will move ", file, " to ", newPath);
filesMoved++;
} else {
console.log("Moving ", file, " would overwrite ", newPath);
}
});
} else {
console.log("Will move ", file, " to ", newPath);
filesMoved++;
}
} else {
(async () => {
await moveFile(file, newPath, {
overwrite: doOverwrite
});
console.log(file, " has been moved to ", newPath);
filesMoved++;
})();
}
}
});
}, {
recursive: doRecursive
});
resolve(filesMoved);
});
};
let filesMoved = await run();
console.log(filesMoved);
mainWindow.webContents.send("moveFilesResult", filesMoved);
});
});
db.close();
});
So the goal here is to wait for all operations on files to be done before sending back a message containing any data relating to what was just done (a counter or the list of files, for example). Are race conditions a thing in NodeJS? How would I go about doing this the right way?