James
James

Reputation: 923

Multiple file upload in Node, force synchronous callback?

I have a form that has a field that can upload multiple images in a single <input> tag. When I access the filesystem using Node, it seems to queue the callback for reading/writing files asynchronously. Because I have multiple files, I have these calls in a for loop, so the value of i is always array.length by the time the callbacks are hit, causing the object to be undefined.

for (var i = 0; i < req.files.photos.length; i++) {
    req.fs.readFile(req.files.photos[i].path, function(err, data) {
        if(err) throw err;

        // i = req.files.photos.length here
        // Test is undefined when the breakpoint on this line is hit for the first time
        var test = req.files.photos[i];

        // Both print "undefined"
        console.log(test.name);
        console.log(test.originalFileName);

        var newPath = __dirname + "/../public/uploads/" + req.files.photos[i].name;

        req.fs.writeFile(newPath, data, function (err) {
            if (err) throw err;

            console.log("it worked");
        });
    });
}

Upvotes: 2

Views: 1629

Answers (2)

Jason Livesay
Jason Livesay

Reputation: 6377

The simplest way is actually to switch to using async/await with promises. If you need to do both things concurrently you can use Promise.all.

It takes a little work to get used to promises or set up with async/await but it is 100% worth it.

import pify from 'pify';
import {readFile, writeFile} from 'fs';

const readFilePr = pify(readFile);
const writeFilePr = pify(writeFile);

async function copyFiles(req) {
 const {photos} = req.files;

  for (let photo of photos) {
    try {
      const image = await readFilePr(photo.path);
      const newPath = `${__dirname}/../public/uploads/${photo.name}`;
      await writeFilePr(newPath);    
    } catch (e) {
      console.error("Problem copying download: ",e);
    }     
  }
}

You may need to set up babel for all of that code to work (assuming there are no typos or anything, I haven't tested it).

Upvotes: 0

treyhakanson
treyhakanson

Reputation: 4911

You can use an IIFE (Immediately Invoked Function Expression) to capture the correct value of i during each iteration of the for-loop:

for (var i = 0; i < req.files.photos.length; i++) {
    (function(j) {
        req.fs.readFile(req.files.photos[j].path, function(err, data) {
            if(err) throw err;
            var test = req.files.photos[j];

            console.log(test.name);
            console.log(test.originalFileName);

            var newPath = __dirname + "/../public/uploads/" + req.files.photos[j].name;

            req.fs.writeFile(newPath, data, function (err) {
                if (err) throw err;
                console.log("it worked");
            });
        });
    }(i));
}

By invoking this function immediately, the value of i will be captured at its current value and stored as a new reference (j) within the function because i is a primitive value. This is a classic example of scope-chain and closure syntax, there's plenty more examples online if you're still having issues

Upvotes: 1

Related Questions