2

I am writing an upload plugin for a WYSIWYG editor that I use in a Meteor app (Meteor 1.2.1). I use Slingshot for uploading files to Amazon S3. My plugin inserts a link in the editor towards the uploaded file once the file is uploaded. Nothing fancy so far.

For a single-file upload this is not a problem. In case of a multiple-file upload, things get harder. What I would like is that after all files are uploaded, I'll end up with an array of filenames and URLS and insert a nice HTML-list containing all links (I don't care about the precise file/link sequence).

Slingshot uses an async function to upload the file:

uploader.send(document.getElementById('input').files[0], function (error, downloadUrl) {
  if (error) {
    // Log service detailed response.
    console.error('Error uploading', uploader.xhr.response);
    alert (error);
  } else {
    Meteor.users.update(Meteor.userId(), {$push: {"profile.files": downloadUrl}});
  }
});

returning the URL of the uploaded file in the callback. In order to collect for instance 5 urls from 5 uploaded files, I think I need the async 'send' function of Slingshot to behave like a sync function.

I hope I understood correctly that Meteor.wrapAsync won't work since all is done on the client. I looked into the javascript Promise, but it is rather overwhelming. And it get's harder to understand it within the Meteor context. The Promise-thing looks promising, but I don't understand which package to use.

Can somebody explain how to tackle (client-side) running multiple (identical) calls to async function in a row, gather the results and uses them after all uploading is done?

Appreciate it,

Cspr

2 Answers2

2

The async library has a method called mapSeries that will allow you to do this. It allows you to iterate over an array with a (possibly) asynchronous function, and will return an array of all the results when the final async task has completed:

var files = [file1, file2, file3];

function iterator(file, callback) {
  uploader.send(file, function(err, downloadUrl) {
    if (err) callback(err);
    else callback(null, downloadUrl);
  });
}

function done(err, results) {
  // results is an array of URLs
}

async.mapSeries(files, iterator, done);

If you don't care about the order in which the requests are processed, you can use the regular async.map method to run the tasks in parallel.

djfdev
  • 5,747
  • 3
  • 19
  • 38
1

Is there anything stopping you from just iterating through the input files, uploading them asynchronously and asking whether we've finished on each callback?

var inputs = document.getElementsByTagName('input');
var urls = [];

for (var i = 0; i < inputs.length; i++) {

  if (inputs[i].files == null) { continue; }

  uploader.send(inputs[i].files[0], function (error, downloadUrl) {

    if (error) {

      console.error('Error uploading', uploader.xhr.response);

    } else {

      urls.push(downloadUrl);

      if (urls.length > inputs.length - 1)
        allFilesUploaded();
    }
  });
}

function allFilesUploaded () {

  Meteor.users.update(Meteor.userId(), {$push: {"profile.files": urls}});
  console.log('All done!');

}
shennan
  • 10,798
  • 5
  • 44
  • 79
  • I have no doubt that this works, and I curse myself that I overlooked this method. But I liked the previous answer a bit better. – Casper Kaandorp Nov 13 '15 at 22:48
  • @CasperKaandorp Fair enough. The other answer recommends a good library. I think you get a better picture of how to deal with asynchronous code out-of-the-box in my answer, but that doesn't necessarily mean it should be marked as correct. That's what voting is for. :-) – shennan Nov 14 '15 at 13:52