thomasb
thomasb

Reputation: 6045

Conditional async callbacks

I'm writing an Electron program which takes a CSV file as input, and does file operations depending on the CSV content and file existence (it's to manage MAME arcade roms).

In order to have a progress bar on the UI side, I have switched the code from fully synchronous (because it was much easier) to asynchronous.

I just cannot find out how to reliably display a message to the user when all the lines in the CSV file are processed, and all the zip files are copied or removed.

Here is a (simplified) sample method:

fs.readFile(file, { 'encoding': 'utf8' }, (err, fileContents) => {
    let fileCsv = csvparse(fileContents);

    let lines = fileCsv.length;
    fileCsv.forEach((line) => {
        lines--;
        let zip = line.name + '.zip';
        let sourceRom = path.join(romset, zip);
        let destRom = path.join(selection, zip);

        this.emit('progress.add', fileCsv.length, fileCsv.length - lines, zip);

        if (fs.existsSync(sourceRom) && !fs.existsSync(destRom)) {
            fs.copy(sourceRom, destRom, (err) => {
                let sourceChd = path.join(romset, game);
                if (fs.existsSync(sourceChd)) {
                    fs.copy(sourceChd, path.join(selection, game), (err) => {
                        if (lines <= 0) { callback(); } // chd is copied
                    });
                } else {
                    if (lines <= 0) { callback(); } // no chd, rom is copied
                }
            });
        } else {
            if (lines <= 0) { callback(); } // no source found or already exists
        }
    });
});

The problem is that the CSV file is processed really fast, but the file are not copied as fast. So it decrements the lines counter to 0, then after each file copy, it finds that it's zero and triggers the callback.

How do I reliably trigger the callback at the end of all these nested callbacks and conditions?

Thanks

Upvotes: 0

Views: 74

Answers (1)

Catalyst
Catalyst

Reputation: 3247

I tried to change the code without massively overwriting your style - assuming there is a reason to avoid things like bluebird, async/await & native Promises, and the async lib.

You need to decrement lines after a line is processed. I pulled the processing logic out into a function to make this clearer:

function processLine({
    sourceRom, destRom, romset, game, callback
}) {
    if (fs.existsSync(sourceRom) && !fs.existsSync(destRom)) {
        fs.copy(sourceRom, destRom, (err) => {
            // *really* should handle this error
            let sourceChd = path.join(romset, game);
            if (fs.existsSync(sourceChd)) {
                fs.copy(sourceChd, path.join(selection, game), (err) => {
                    // *really* should handle this error
                    callback();
                });
            } else {
               callback();
            }
        });
    } else {
        callback() // no source found or already exists
    }
}

fs.readFile(file, { 'encoding': 'utf8' }, (err, fileContents) => {
    let fileCsv = csvparse(fileContents);

    let lines = fileCsv.length;
    fileCsv.forEach((line) => {
        let zip = line.name + '.zip';
        let sourceRom = path.join(romset, zip);
        let destRom = path.join(selection, zip);

        this.emit('progress.add', fileCsv.length, fileCsv.length - lines, zip);

        processLine({ sourceRom, destRom, game, romset, callback: () => {
            lines--;
            if (lines <= 0) {
                callback();
            }
        }})
    });
});

Upvotes: 1

Related Questions