javascriptnode.jsexpressasynchronousarchiverjs

Can't download dynamically created ZIP archive


I'm trying to dynamically write an output from MySQL queries to archive. Here's my code:

var async = require("async");
var mysql = require("mysql");
var express = require("express"); 
var archiver = require("archiver");

var app = express();

var connection = mysql.createConnection({
   host     : 'localhost',
   user     : 'root',
   password : 'password',
   database : 'test'
});

app.get('/file', (req, res) => {

    res.writeHead(200, {
        'Content-Type': 'application/zip',
        'Content-disposition': 'attachment; filename=archive-name.zip'

    var zip = archiver('zip', {
        zlib: { level: 9 } 
    });

    zip.pipe(res); 

    zip.on('end', function() {
        console.log('Archive wrote %d bytes', zip.pointer());
    });

    const queriesArray = ["SELECT * FROM tb1", "SELECT * FROM tb2"];

    async.forEachOf(queriesList, (query) => {
        connection.query(query, (err, results) => {
            if(!err) {
                zip.append(JSON.stringify(results), {name: `${query}.txt`})
            }
            else {
                console.log("Error while performing Query");
            }
        })
    }, function(err) {
           if (err) {
               console.log("error")
           }
           else {
               zip.finalize();
           }
       }) 
})

const port = process.env.PORT || 7000;
app.listen(port, () => {
    console.log('Listen on port ' + port);
})

This code is supposed to create a zip archive with multiple text files. The user visiting /file page must be presented with a file download prompt but it seems like zip.finalize() isn't working so I can't download created archive (download starts when I'm visiting /file but doesn't finish). Why won't my code work?


Solution

  • It is because your callback function is never called as forEachOf doesn't know it has finished all the async tasks.

    The iteratee function has a final argument that is a callback function that you must call in order for it to know that that particular async process has finished, but you never use it.

    https://caolan.github.io/async/global.html#AsyncFunction

    An "async function" in the context of Async is an asynchronous function with a variable number of parameters, with the final parameter being a callback. (function (arg1, arg2, ..., callback) {}) The final callback is of the form callback(err, results...), which must be called once the function is completed.

    So to fix your code make sure to call the callback method of your iteratee function inside the callback of your query call

    async.forEachOf(queriesList, (query,key,callback) => {
        connection.query(query, (err, results) => {
            if(!err) {
                zip.append(JSON.stringify(results), {name: `${query}.txt`})
            }
            else {
                console.log("Error while performing Query");
            }
            callback();
        })