Увеличение счетчика асинхронно в NodeJS - PullRequest
0 голосов
/ 14 апреля 2020

Я боролся с этим уже несколько часов. По сути, я занимаюсь перемещением файлов на основе их имен в связи с базой данных SQLite. Это в приложении Electron, где я использую IP C. Когда средство визуализации отправляет сообщение в основной процесс для перемещения файлов, оно затем прослушивает ответ, прежде чем отобразить его в приложении.

Я очень наивно увеличил счетчик во многих местах. sh Я считаю, сколько файлов будет перемещено или перемещено, чтобы я мог информировать пользователя. Проблема в том, что независимо от того, что я пытаюсь (Promises, asyn c, await), при отправке счетчик никогда не будет корректным. Это всегда 0, потому что сообщение отправляется до его увеличения.

Короче говоря, мне нужно дождаться полного увеличения счетчика перед отправкой сообщения. Для этого логично было бы дождаться выполнения dir.files () или files.forEach (), но я не смог заставить все это работать. Примечание: при печати счетчика в fs.access () сразу после приращения значения верны (1, 2, 3, ...), при печати вне этого асинхронного вызова всегда выводится 0.

Вот полный код:

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);
});

А вот попытка, которую я предпринял.

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();
});

Итак, цель здесь - дождаться выполнения всех операций с файлами перед отправкой назад. сообщение, содержащее любые данные, относящиеся к тому, что только что было сделано (например, счетчик или список файлов). Гоночные условия - вещь в NodeJS? Как бы я go сделал это правильно?

1 Ответ

0 голосов
/ 14 апреля 2020

Я преобразовал все свои асинхронные обратные вызовы в обещания и, наконец, все заработало, теперь это кажется намного понятнее. Вот код для любого, кто спотыкается об этом:

Первая версия

ipcMain.on("moveFiles", (event, moveTo, moveFrom, databaseFile, doOverwrite, doRecursive, subFolder, doTestRun) => {
    const db = new sqlite3.Database(databaseFile, sqlite3.OPEN_READONLY, (cErr) => {
        if (cErr) {
            console.error("Could not connect to the database");
            throw cErr;
        } else {
            console.log("Connected to the database")
        }
    });

    let query = `...`;
    let regex = /.../;

    const getAll = (query, params = []) => {
        return new Promise((resolve, reject) => {
            db.all(query, params, function (qErr, rows) {
                if (qErr) {
                    console.error("Error running the query");
                    reject(qErr);
                } else {
                    resolve(rows);
                }
            });
        });
    };

    const getFiles = (path, type, options) => {
        return new Promise((resolve, reject) => {
            dir.files(path, type, function (fErr, files) {
                if (fErr) {
                    console.error("Error iterating through files");
                    reject(fErr);
                } else {
                    resolve(files);
                }
            }, options);
        });
    };

    // Resolves to false if the file doesn't exist, true if it does
    const checkFileExists = (path) => {
        return new Promise(resolve => {
            fs.access(path, fs.F_OK, function (aErr) {
                if (aErr) {
                    resolve(false);
                } else {
                    resolve(true);
                }
            });
        });
    };

    getAll(query).then(async function (rows) {
        let dbNameToFolder = {};

        for await (const row of rows) {
            dbNameToFolder[row["mediaType"]] = {
                "local_folder": row["local_folder"],
                "type": row["type"]
            };
        }

        return dbNameToFolder;
    }).then(function (assoc) {
        getFiles(moveFrom, "file", {recursive: doRecursive}).then(async function (files) {
            let filesToMove = [];

            for await (const file of files) {
                let filename = path.basename(file);
                let matches = regex.exec(filename);

                if (matches !== null && matches.length > 1 && assoc.hasOwnProperty(matches[2])) {
                    let newPath = path.join(moveTo, assoc[matches[2]]["type"], assoc[matches[2]]["local_folder"], subFolder, filename);

                    if (!doOverwrite) {
                        await checkFileExists(newPath).then(function(exists) {
                            if (!exists) {
                                console.log("Will move ", file, " to ", newPath);

                                filesToMove.push({
                                    "oldPath": file,
                                    "newPath": newPath
                                });
                            } else {
                                console.log("Moving ", file, " would overwrite ", newPath);
                            }
                        });
                    } else {
                        console.log("Will move ", file, " to ", newPath);

                        await filesToMove.push({
                            "oldPath": file,
                            "newPath": newPath
                        });
                    }
                }
            }

            return filesToMove;
        }).then(function(filesToMove) {
            mainWindow.webContents.send("moveFilesResult", filesToMove);

            if (doTestRun) {
                console.log("Not moving files");
            } else {
                console.log("Moving files");

                for (const file of filesToMove) {
                    (async () => {
                        await moveFile(file["oldPath"], file["newPath"], {
                            overwrite: doOverwrite
                        });
                    })();
                }
            }
        });
    });
});

Мне больше не нужен счетчик, я просто получаю длину массива в процессе рендеринга. Я правильно использую обещания и жду / asyn c сейчас?


Вторая версия

Это результат после переключения всего на await. Я должен был сделать слушателя из ipcMain.on() async, чтобы иметь возможность await. Может быть, лучше обернуть все мои await вызовы в отдельный async блок?

Я читал, что использование async оборачивает функцию в Promise, и мне нужно было использовать await в функции convertPaths для массива filesToMove это хорошо?

Я также удалил await на втором filesToMove.push(), поскольку это казалось ненужным, но я не уверен.

Вы упомянули необходимость .catch(), если я использую .then(). Теперь, когда я не использую .then(), как мне обрабатывать отклонения обещания, кроме как с .catch()?

ipcMain.on("moveFiles", async (event, moveTo, moveFrom, databaseFile, doOverwrite, doRecursive, subFolder, doTestRun) => {
    const db = new sqlite3.Database(databaseFile, sqlite3.OPEN_READONLY, (cErr) => {
        if (cErr) {
            console.error("Could not connect to the database");
            throw cErr;
        } else {
            console.log("Connected to the database")
        }
    });

    let query = `...`;
    let regex = /.../;

    const getAll = (query, params = []) => {
        return new Promise((resolve, reject) => {
            db.all(query, params, function (qErr, rows) {
                if (qErr) {
                    console.error("Error running the query");
                    reject(qErr);
                } else {
                    resolve(rows);
                }
            });
        });
    };

    const getFiles = (path, type, options) => {
        return new Promise((resolve, reject) => {
            dir.files(path, type, function (fErr, files) {
                if (fErr) {
                    console.error("Error iterating through files");
                    reject(fErr);
                } else {
                    resolve(files);
                }
            }, options);
        });
    };

    const convertPaths = async (assoc, files) => {
        let filesToMove = [];

        for (const file of files) {
            let filename = path.basename(file);
            let matches = regex.exec(filename);

            if (matches !== null && matches.length > 1 && assoc.hasOwnProperty(matches[2])) {
                let newPath = path.join(moveTo, assoc[matches[2]]["type"], assoc[matches[2]]["local_folder"], subFolder, filename);

                if (!doOverwrite) {
                    await fs.promises.access(newPath, fs.constants.F_OK).then(() => console.log("Moving ", file, " would overwrite ", newPath)).catch(function () {
                        // If we catch an error, it's because the file doesn't exist, so we can move it without overwriting
                        console.log("Will move ", file, " to ", newPath);

                        filesToMove.push({
                            "oldPath": file,
                            "newPath": newPath
                        });
                    });
                } else {
                    console.log("Will move ", file, " to ", newPath);

                    filesToMove.push({
                        "oldPath": file,
                        "newPath": newPath
                    });
                }
            }
        }

        return filesToMove;
    };

    let rows = await getAll(query);
    let assoc = {};

    for (const row of rows) {
        assoc[row["mediaType"]] = {
            "local_folder": row["local_folder"],
            "type": row["type"]
        };
    }

    let files = await getFiles(moveFrom, "file", { recursive: doRecursive });
    let filesToMove = await convertPaths(assoc, files);

    if (!doTestRun) {
        for (const file of filesToMove) {
            await moveFile(file["oldPath"], file["newPath"], { overwrite: doOverwrite });
        }

        console.log("Done moving files");
    } else {
        console.log("Not moving files");
    }

    mainWindow.webContents.send("moveFilesResult", filesToMove);
});

Третья версия

Я также сделал третья версия, где я удаляю (что я считаю) ненужные функции - и добавил Promise для подключения к базе данных. Как это выглядит сейчас?

ipcMain.on("moveFiles", async (event, moveTo, moveFrom, databaseFile, doOverwrite, doRecursive, subFolder, doTestRun) => {
    let query = `...`;
    let regex = /.../;

    const connect = (databaseFile, mode) => {
        return new Promise((resolve, reject) => {
            const db = new sqlite3.Database(databaseFile, mode, function (cErr) {
                if (cErr) {
                    reject(cErr);
                } else {
                    resolve(db);
                }
            });
        });
    };

    const getAll = (db, query, params = []) => {
        return new Promise((resolve, reject) => {
            db.all(query, params, function (qErr, rows) {
                if (qErr) {
                    console.error("Error running the query");
                    reject(qErr);
                } else {
                    resolve(rows);
                }
            });
        });
    };

    const getFiles = (path, type, options) => {
        return new Promise((resolve, reject) => {
            dir.files(path, type, function (fErr, files) {
                if (fErr) {
                    console.error("Error iterating through files");
                    reject(fErr);
                } else {
                    resolve(files);
                }
            }, options);
        });
    };

    let db = await connect(databaseFile, sqlite3.OPEN_READONLY);
    let rows = await getAll(db, query);
    let assoc = {};

    for (const row of rows) {
        assoc[row["mediaType"]] = {
            "local_folder": row["local_folder"],
            "type": row["type"]
        };
    }

    let files = await getFiles(moveFrom, "file", { recursive: doRecursive });

    let filesToMove = [];

    for (const file of files) {
        let filename = path.basename(file);
        let matches = regex.exec(filename);

        if (matches === null || matches.length <= 1) continue;
        if (!assoc.hasOwnProperty(matches[2])) continue;

        let newPath = path.join(moveTo, assoc[matches[2]]["type"], assoc[matches[2]]["local_folder"], subFolder, filename);

        if (!doOverwrite) {
            await fs.promises.access(newPath, fs.constants.F_OK)
                .then(() => console.log("Moving ", file, " would overwrite ", newPath))
                .catch(function () {
                // If we catch an error, it's because the file doesn't exist, so we can move it without overwriting
                console.log("Will move ", file, " to ", newPath);

                filesToMove.push({
                    "oldPath": file,
                    "newPath": newPath
                });
            });
        } else {
            console.log("Will move ", file, " to ", newPath);

            filesToMove.push({
                "oldPath": file,
                "newPath": newPath
            });
        }
    }

    if (!doTestRun) {
        for (const file of filesToMove) {
            await moveFile(file["oldPath"], file["newPath"], { overwrite: doOverwrite }).catch(function(mErr) {
                console.error(mErr);
            });
        }

        console.log("Done moving files");
    } else {
        console.log("Not moving ", filesToMove.length, " files");
    }

    mainWindow.webContents.send("moveFilesResult", filesToMove);
});
...