Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to wait for a loop creating streams to finish before saving?

I have a problem with knowing when the loop is finished,

app.post('/api/books', upload.array('images'), function(req, res) {
    let book = req.body.book;
    let arr = [];

    // GridFS get connection with DB   
    var gfs = gridfsstream(conn.db);
    var writestream;

    for (i = 0; i < req.files.length; i++) {
        writestream = gfs.createWriteStream({
            filename: req.files[i].originalname
        });
        fs.createReadStream('uploads/' + req.files[i].filename).pipe(writestream);
        writestream.on("close", function(file) {
            console.log(file.filename + "stored successfully into mongodb using gridfs");
        });
        writestream.on("error", function(file) {
            console.log(file.filename + "not stored into mongodb using gridfs");
        });

        base64(writestream.name, function(response) {
            arr.push(response);
        });
    }

    book.images = arr;
    Book.addBook(book, function(err, book) {
        if (err) {
            throw err;
        }
        res.json(book);
    });
});

The problem is: The array arr is empty when I do

book.images = arr 

I need wait the for loop be finished , but how can i do that?

I know this works because i already put a console.log() and works correctly

base64(writestream.name, function(response) {
    arr.push(response);
});
like image 627
Diego Oliveira Avatar asked Nov 18 '25 01:11

Diego Oliveira


1 Answers

Probably best to use Promise.all here, but you need to wrap each of the "files" in a Promise and return resolved based on when the writeStream in each is completed or errors:

app.post('/api/books', upload.array('images'), function(req,res) {
  let book = req.body.book;
  var gfs = gridfsstream(conn.db);

  Promise.all(
    req.files.map(function(file) => {
      return new Promise(function(resolve,reject) {
        var writestream = gfs.createWriteStream({
          filename: file.originalname
        });

        fs.createReadStream('uploads/'+file.filename).pipe(writestream);

        writestream.on("error",reject);
        writestream.on("close",function() {
           base64(writestream.name, function(response) {
             resolve(response);
           });
        });

      })
    })
  )
  .then(function(images) {
    book.images = images;
    Book.addBook(book,function(err,book) {
      if (err) throw err; // or whatever
      res.json(book)
    });
  })
  .catch(function(err) => {
    // Deal with errors
  });
});

That involves no additional dependencies, however you could alternately use async.map as an additional dependency:

app.post('/api/books', upload.array('images'), function(req,res) {
  let book = req.body.book;
  var gfs = gridfsstream(conn.db);

   async.map(
     req.files,
     function(file,callback) {
        var writestream = gfs.createWriteStream({
          filename: file.originalname
        });

        fs.createReadStream('uploads/'+file.filename).pipe(writestream);

        writestream.on("error",callback);
        writestream.on("close",function() {
           base64(writestream.name, function(response) {
             callback(null,response);
           });
        });                      
     },
     function(err,images) {
       if (err) throw err;

       book.images = images;
       Book.addBook(book,function(err,book) {
         if (err) throw err; // or whatever
         res.json(book)
       });
     }
   );
});

So they look pretty similar, and basically they are doing the same thing. In each case the "loop" is now a .map() which passes in the current filename as an argument and returns an array of the transformed response, which is in this case the output from your base64 function. They key here is that the resolve or callback is basically in control of when things happen.

In the case using Promise.all, the .map() is a basic JavaScript .map() function on the array, which is essentially returning an "array of Promise" which all implement the reject and resolve functions which are then called by the respective handlers on the stream.

Here the "Promises" all execute and return there results within the Promise.all and pass the output array to the block with .then(), which has the content and can then pass to your method to update/create as may be the case.

In the async.map example, this rather uses a callback argument which is again supplied to the event handlers on the stream. In exactly the same way, the final block receives the output or error, and can again pass to your method to persist the data.

There is a slight difference in the actual execution of calls, but both essentially apply the same principle that we "signal" output has completed to the mechanism providing the "loop", so that we know when "all" have completed.

like image 142
Neil Lunn Avatar answered Nov 20 '25 17:11

Neil Lunn



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!