Collection.find() Doesn't Work in Server Callbacks

I’m working on allowing the user to upload documents to Amazon S3. My sequence of events is as follows:

  1. Client calls server-side insert method when user selects a file.
  2. Server inserts upload details to an ActiveUploads collection, publishes ActiveUploads, and returns the upload ID.
  3. Client subscribes to ActiveUploads collection with a selector for the current upload (as callback to #1).
  4. Client calls server method to initiate the S3 upload (as callback to #1).
  5. Server creates an S3 object, attaches an httpUploadProgress listener to the upload manager, sends the upload, and reports error or success through ActiveUploads.
  6. Client will listen for error or success in ActiveUploads and updates the view when upload is complete (as callback to #4, I haven’t actually written this step yet).

My problem lies in step #5. ActiveUploads.find() doesn’t return anything in the upload httpUploadProgress callback or the send callback. It also prevents any subsequent functions in the callbacks from running. Here’s some code for step #5:

console.log(ActiveUploads.find().count());
console.log('START');

bucket.upload({ 
    Key: data.key, 
    ContentType: data.filetype, 
    Body: data.file 
}).on('httpUploadProgress', function(evt) {
    console.log('BEFORE 1');
    console.log(ActiveUploads.find().count());
    console.log('AFTER 1');
}).send((error, result) => {
    console.log('BEFORE 2');
    console.log(ActiveUploads.find().count());
    console.log('AFTER 2');
});
console.log(ActiveUploads.find().count());
console.log('END');

Count and START, count and END, BEFORE 1, and BEFORE 2 are logged; AFTER 1 and AFTER 2 are not logged, and neither are the counts above them (the same happens if I run ActiveUploads.find() without the count()).

Does this make sense? I know the httpUploadProgress listener and send() function are going to act asynchronously with respect to the rest of everything that’s going on, but shouldn’t they still be able to find things in the collection? I’m not deleting the collection or removing anything. Any idea what’s going on here?

If this code above is server-side, you’ll want to use Meteor.wrapAsync to convert async functions to synchronous. Meteor.wrapAsync is strict and the function you pass it must accept a callback as its last parameter, with the arguments (error, result).

So, AWS S3 putObject would work like this:

const s3 = new AWS.S3();
const putObject = Meteor.wrapAsync(s3.putObject, s3);

try {
  const result = putObject({
    Key: data.key,
    ContentType: data.filetype,
    Body: data.file,
  });
} catch (e) {
  // handle AWS error
}

// Now this code executes after upload is complete

Is there a reason for me to use putObject instead of upload?

upload returns an AWS ManagedUpload object that I use to track the upload progress and to send the upload. wrapAsync returns the result that is sent to the callback of upload instead of the result of upload itself (the ManagedUpload object). This is a problem because I need to add a listener to the ManagedUpload object before calling send so that I can track the upload progress. How can I actually send the upload when wrapAsync returns the result that is sent to the upload callback instead of the ManagedUpload object that I’m expecting?

Ah! I wasn’t even aware of upload actually. So yeah, that would work too, just replace what I put in my code.

const upload = Meteor.wrapAsync(s3.upload, s3);

EDIT: Ah, right, there’s a callback for the upload progress. Hmm…that’s tricky. I’m honestly not sure, since I’ve never used the ManagedUpload object.

I think it’s something to do with ActiveUploads.find(). For some reason it just breaks. It doesn’t return anything, and it blocks anything after it from running. What could cause that? I know from the first console log that ActiveUploads has things in it. I’m not removing anything, so the count increments any time I insert details about a new upload (anytime I try to upload something a different method adds details to ActiveUploads). The callbacks for the AWS functions do run, so ActiveUploads.find() should work just fine. Any idea what’s going on there?

The error I’m getting is:

[Error: Can't wait without a fiber]

Got it to work by wrapping the callbacks with Meteor.bindEnvironment(). Thanks for the help!

bucket.upload({ 
    Key: data.key, 
    ContentType: data.filetype, 
    Body: data.file 
}).on('httpUploadProgress', Meteor.bindEnvironment((evt) => {
    console.log(ActiveUploads.find().count());
})).send(Meteor.bindEnvironment((error, result) => {
    console.log(ActiveUploads.find().count());
}));
1 Like