Search code examples
node.jsrequestunzip

Request and writeStream filename


I'm sorry if I'm missing something - I'm still learning Node's logic (esp. asynchronous jobs).

I want to download a zip file, then save it and unzip it. It looks like this:

request(URL)
    .pipe(fs.createWriteStream(/path/to/filename))
    .on('close', function () {
        // I want to unzip and I need access to filename
    });

Now the thing is, if I set a filename variable beforehand, since it's in a loop, by the time the file gets downloaded, filename will have the value of the last loop iteration.

Of course, I see we can do it by defining a function that would do all this, like fecthAndUnzip(URL,path). But my question is rather design-related: is it the only way we can do it in Node, or is there a better one? For example, can I access the filename in the on('close') event and if so, should I? Thanks!


Solution

  • Defining a separate function is definitely not the only way and there is a much better one.

    You aren't showing the full code but I assume due to the behavior you are using var to define your variable. As in var filename = 'something'. When you do this you are defining the filename variable in the global [function] scope. This means it will be available to every part of the function regardless of where it was defined. In your case even though you define the variable in the loop it is scoped in the entire function, so every iteration you are actually overwriting that variable.

    There is a good stackoverflow answer about scopes here if you want to learn more or if I didn't explain it well enough: https://stackoverflow.com/a/11444416/6359249

    The solution would be to define a variable in the current block scope. If you are using a newer version of node.js that implement ES6 you can use let (or const for constant variables). Unlike var, this will define a function in the current block. If you define it in a loop it will only be accessible to that block and won't be changed. For example:

    for (let i = 0; i < files.length; i++) {
      let filename = files[i];
      // Do stuff
    }
    

    Notice let i = 0. The same scoping concepts apply to loop variables. It's a common problem for people to use var i = 0 and become confused when using an asynchronous function and discovering i is the last value of i.

    If you are not using ES6 you can define an anonymous function every loop:

    for (var i = 0; i < files.length; i++) {
      var filename = files[i];
      (function (file) {
        // Do stuff
      })(filename);
    }
    

    Which is the same as the solution you described except you are creating and executing the function on the fly. If you aren't using ES6 for some reason then your original answer of creating a separate function is probably the best solution.