4

I have the following piece of code, but due to it's async behavior (I suppose), the callback is called twice. I'm using node-unzip to unzip a file I download from the internet.

function DownloadAndExtract(file, callback) {
    log.debug("Starting download of "+file);
    fse.ensureDirSync(tempPath);
    var extractor = unzip.Extract({path: tempPath});
    extractor.on("close", function() {
        log.debug("Done downloading "+file);
        return callback(null, file);
    });
    extractor.on("error", function (err) {
        log.error("Extracting download "+file+": "+JSON.stringify(err, null, "\t"));
        return callback(err, null); // THIS IS LINE 274
    });

    var url = "";
    if(file == "WONEN") {
        url = "https://example.com/file1.zip";
    }else if(file == "BOG") {
        url = "https://example.com/file2.zip";
    }

    if(url != "") {
        request
            .get(url)
            .on("error", function (err) {
                return callback(err, null);
            })
            .pipe(extractor);
    }else{
        return callback(new Error("Invalid file indicator: '"+file+"'"), null);
    }
}

I expected return to actually quit all running async functions but that is obviously nonsense. Now, the error I keep getting is the following:

/home/nodeusr/huizenier.nl/node_modules/async/lib/async.js:30
        if (called) throw new Error("Callback was already called.");
                          ^
Error: Callback was already called.
    at /home/nodeusr/huizenier.nl/node_modules/async/lib/async.js:30:31
    at /home/nodeusr/huizenier.nl/node_modules/async/lib/async.js:251:21
    at /home/nodeusr/huizenier.nl/node_modules/async/lib/async.js:575:34
    at Extract.<anonymous> (/home/nodeusr/huizenier.nl/realworks.js:274:10)
    at Extract.emit (events.js:129:20)
    at Parse.<anonymous> (/home/nodeusr/huizenier.nl/node_modules/unzip/lib/extract.js:24:10)
    at Parse.emit (events.js:107:17)
    at /home/nodeusr/huizenier.nl/node_modules/unzip/lib/parse.js:60:12
    at processImmediate [as _immediateCallback] (timers.js:358:17)

The output of the log.error() call is the following: 21-02-2015 03:00:05 - [ERROR] Extracting download WONEN: {} so I'm quite confused. There isn't really an error, then why is the event emitted?

How would I prevent the callback from being called twice here? Contacting the creator of the package or create a work around?

Code calling DownloadAndExtract

async.parallel([
    function (callback) {
        DownloadAndExtract("WONEN", callback);
    },
    function (callback) {
        DownloadAndExtract("BOG", callback);
    }
],
function (err, done) {
    if(err) return log.error("Update entries: "+JSON.stringify(err, null, "\t"));
    // Do different logic if no error
});

Edit

One of my attempts is declaring a var callbackAlreadyCalled = false within the function, and at any given point where I call the callback, I do

if(!callbackAlreadyCalled) {
    callbackAlreadyCalled = true;
    return callback(callback params);
}

Is this a good approach or could I handle it in a better way?

Edit 2

Already found out that the empty error is caused by errors not working properly when using JSON.stringify(), however, problem doesn't change.

Ruben Rutten
  • 1,659
  • 2
  • 15
  • 30
  • Don't stringify Error objects, often the result will be an empty object {}. Console.log or console.error the error object directly instead. – Yuri Zarubin Feb 21 '15 at 17:16
  • I've just found a solution to prevent this from happening (see: http://stackoverflow.com/a/18391400/2248679), however, this is not the main problem I have – Ruben Rutten Feb 21 '15 at 17:17
  • I think you should provide the async call to DownloadAndExtract – JuniorCompressor Feb 21 '15 at 17:30
  • Just to clear this up, the problem ONLY occurs in case of an error, where as in other cases it works fine. The problem is that `DownloadAndExtract` continues to work when the callback is already called, and that is the problem I have to tackle. – Ruben Rutten Feb 21 '15 at 17:37

1 Answers1

0

It looks like you have waterfall error calls. When you call the request, it calls the extractor then if there is an error it calls the on_error of the extractor and then the on_error of the request.

Try to unify your returns from errors or call it only once. Make a test removing one of your "on('error'" validation.

Cleriston
  • 750
  • 5
  • 11