Call function (mongo.insert) inside async await promise chain

hi all,

I have server side code, I use ES 7 await async function so my code runs in sync fashion, this works great. But now I wanted to have some logging (I write into a mongo API_LOGS collection server side). But this is not allowed, I get the famous ERROR while reloading the new app: [Error: Meteor code must always run within a Fiber. Try wrapping callbacks that you pass to non-Meteor libraries with Meteor.bindEnvironment.] error.

If I remove the comments it breaks… How to solve this? thank you

//use ES7 await function so this code will run in synchronous mode
    return await qsocks.Connect(engineConfig)
        .then(function(global) {
            // console.log('connected to Qsocks');
            _global = global;
            return global.openDoc(appId, '', '', '', true) //global.openDoc(appId), this code opens the app without data, that is faster!
        })
        .then(function(doc) {
            console.log('** getAppsViaEngine, QSocks opened and now tries to set the script for appId: ', appId);
            return doc.getScript()
                .then(function(script) {
                    console.log('get Script success, ', script);

                    // var call = {};
                    // call.action = 'Replace script'
                    // call.request = 'We extracted the following script from the app: ' + script;
                    // REST_Log(call);
                    // if you want to replace the database connection per customer use the script below.
                    //return doc.setScript(script.replace(scriptMarker, scriptReplace)).then(function (result) {
                    //you can also change the sense database connection: https://github.com/mindspank/qsocks/blob/master/examples/App/create-dataconnection.js
                    return doc.setScript(script) //we now just include the old script in this app
                        .then(function(result) {

ps: QSocks is a NPM module to connect to the qlik sense engine API. (A reporting tool)

my log function is like this

import { Mongo } from 'meteor/mongo';
 
export const APILogs = new Mongo.Collection('apiLogs');

export function REST_Log(call){
	call.createDate = new Date();
	call.createdBy = null; //this.userId;
	APILogs.insert(call);

}

apparently, I can’t insert into mongo during a async await?

I had a similar issue, have a look here

1 Like

Yes Thank you. But i thought mongo inserts are synchronous. So without callback, and are meteor side code (not external) so how would an collection.insert look like?

Hi all,

Is this a bug? using async await, it should be possible to do a normal mongo.insert right?

thanks

Not sure I understood your question. However, yes insert are synchronous. In my case, I had the same error as you did because when the callback from the api call was fired, I did not bind the environment to the callback.

You can also use Promise.await(…) to make the promise synchronous and then proceed with the insertion.

I can provide an example of that if you need.

Thank you.

Key difference here is that I don’t use an external lib and therefore I would think I don’t need any tricks… (I don’t get any error with my mongo insert)

Or does the async await break normal meteor behavior?

Verstuurd vanaf mijn iPhone

@robfallows should I use your trick with raw collections again here?

Collection.rawCollection()

But why does standard meteor does not work with async and collection.insert?

Are you using Meteor’s inbuilt Promises/async/await or a third-party solution?

Hi, all the inbuild.

But within the promise, I do connect to an external lib. (that thing alone does not cause the issue, only when I try to insert something in mongo)

Verstuurd vanaf mijn iPhone

qsocks is using a third party Promise library, which will not play nicely with fibers/futures in Meteor, so you will need to use promised Mongo methods. The current MongoDB driver used in Meteor 1.4.1.1 uses Promises natively when used in the non-callback mode. Access to those methods will need to be done via Collection.rawCollection() as you have suggested. Instead of:

export function REST_Log(call){
  call.createDate = new Date();
  call.createdBy = null; //this.userId;
  APILogs.insert(call);
}

You should use something like:

export function REST_Log(call){
  call.createDate = new Date();
  call.createdBy = null; //this.userId;
  return APILogs.rawCollection().insert(call); // return a Promise
}

However, you should check the API docs for insert in case your call is not compatible with the native functionality (you may need to explicitly define a string _id), or you need to amend the options.

3 Likes

Thanks Rob, this works perfectly!

PS: I am pretty sure my code worked in 1.3 so apparently meteor changed something here…

ps: I now use this to prevent errors on the client

export function REST_Log(call, userId = 'Not defined') {
    call.createDate = new Date();
    call.createdBy = userId
    if (Meteor.isServer) {
        APILogs.rawCollection().insert(call);
    } else {
        APILogs.insert(call);

    }
    return;
2 Likes

Using .rawCollection() worked for me also, I think a note on this should be in the docs at least as I hit a roadblock with this. What do you think?

That’s a tricky one, because it would have been OK with Meteor’s inbuilt Promises. The “problem” here is there are so many (too many?) Promise libraries in npmland.

Fair point but I came across this headache using native es6 promises. It would save other newbs like me a few strands of hair if it were written down somewhere :smile_cat:

I raised the issue on StackOverflow here