Meteor.wrapAsync for HTTP call server side


#1

Okay, so I’m totally new to Meteor.wrapAsync, so far I could manage to do everything synchronous :wink:

Following scenario:

I upload a file to a server, the file is a pdf which needs to be base64 encoded.

So I got this:

convertFile = () ->
    require('file-base64').encode(file,(err,b64Str) ->
      if err then throw err
      opts.data.file = b64Str
      HTTP.post(process.env.UPLOAD_URL,opts)
    )
  convertFileSync = Meteor.wrapAsync(convertFile)
  convertFileSync()

The only problem is the HTTP call which throws an “Meteor code must always run within a Fiber” error.
I had the same problem with the encoding function as well, but managed to resolve that by using Meteor.wrapAsync on it (hopefully I used it right at least for this one).

How do I get to make the HTTP call now without getting the error?

And more interesting: How do I get a callback from that HTTP call?
According to the docs, I can do either
HTTP.post(URL,opts,callback)
or
result = HTTP.post(URL,opts)

Tried both, but I guess it fails before the call is even made…

Any ideas?


#2

Hi,

In general I learned this (I also need sync code on the server)

  • Meteor runs everything by default in sync mode
  • If you use external non meteor modules, like npm, then you have to start using wrap async etc.

E.g. these functions are executed in order https://github.com/QHose/QRSMeteor/blob/master/imports/api/server/QRSFunctionsApp.js#L43

Even though, this baby uses an external module: See https://github.com/QHose/QRSMeteor/blob/master/imports/api/server/QRSFunctionsApp.js#L69 for examples with async.

Regards Martijn


#3

I didn’t test, but it should be something like this:

import base64 from 'file-base64';

const encodeSync = Meteor.wrapAsync(base64.encode);
try {
  const base64str = encodeSync(file);
  const opts = { data: { file: base64str }}; 

  const response = HTTP.post(process.env.UPLOAD_URL, opts);
}
catch (ex) {
  // handle error
}

#4

Works like a charm, thanks.

So what needs to be wrapped in async is the first function that returns something asynchronously, did I get that right now?


#5

Yes, the first non meteor API that you use. In my case, all the qsocks calls have to be wrapped. Or even better, use the async await ES7 approach

and… like yann shows, his http is a meteor http call, and using this meteor http you can write it like sync code…


#6

So assuming I wouldn’t be using the HTTP class but my own crappy Ajax function, I would need to wrap that as well? or since it is running inside a wrapped function it doesn’t need to be wrapped anymore?


#7

In theory you should bind each callback of your crappy function. The key thing is the word callback. Meteor uses fibers, its own way of waiting for a async stuff… and you can’t mix to mechanisms…

I am not sure when to use bindenvironment versus wrapasync, they are a bit the same and use each other under the hood.


#8

Yeah I figured that as well… Kinda part of the confusion.
But thanks for bringing light to the darkness :smiley:


#9

I do have one more question.

I am clear on how to wrap my functions correctly now (in fact it’s all working now, thanks to your help :wink: )

What do I do if my callback looks like this:

someLibrary = require('someLibrary')

someLibrary(param1,param2).then(function({
   console.log("I am asynchronous!")
})

This actually works without wrapping it as long as it is console.log or similar.
I tried calling the function from above:

someLibrary = require('someLibrary')

someLibrary(file,"someIDForDatabaseTransaction").then(function({
   convertFile(file)
})

And THIS throws the known error “Meteor code must always run blah blah blah”.

The function that is being called asynchronously is the “then()” right? how would you wrap that?

I tried wrapping the whole thing:

someLibrarySync = Meteor.wrapAsync(someLibrary)

someLibrarySync(file,"someIDForDatabaseTransaction").then(function({
   convertFile(file)
}

But that didn’t change anything other than making the code look uglier and uglier and throwing an error inside the library because it can’t reference itself anymore.

EDIT:
Nevermind, passing the reference to Meteor.wrapAsync did the trick!