Nyxynyx
Nyxynyx

Reputation: 63619

Async Issue using NPM Package and Meteor.wrapAsync

When using a function client.call from an npm module (node-celery), it appears that the callback function of client.call is not executed.

Meteor.methods({
    'estimates.request'(data) {
        var celery = require('node-celery')
        var client = celery.createClient({...})

        client.on('connect', function() {
            console.log('connected');                   // this is executed
            client.call('proj.tasks.register', [name],
                function(err, result) {
                    console.log('result: ', result);    // this is not executed
                    client.end();
                    return result;
                }
            );
        });
    }
});

Tried wrapping client.call in Meteor.wrapAsync:

callFunc = client.on('connect', function() {
    console.log('connected');                   // this is executed
    client.call('proj.tasks.register', [name],
        function(err, result) {
            console.log('result: ', result);    // this is not executed
            client.end();
            return result;
        }
    );
});

callFuncSync = Meteor.wrapAsync(callFunc)
callFuncSync()

but this throws an error in the Meteor server console:

err:  [Error: Meteor code must always run within a Fiber. Try wrapping callbacks that you pass to non-Meteor libraries with Meteor.bindEnvironment.]
err:  { [Error: read ECONNRESET] code: 'ECONNRESET', errno: 'ECONNRESET', syscall: 'read' }

Question: How should we use Meteor.bindEnvironment to fix this issue?

Upvotes: 0

Views: 94

Answers (2)

MasterAM
MasterAM

Reputation: 16478

From the docs,

Wrap a function that takes a callback function as its final parameter. The signature of the callback of the wrapped function should be function(error, result){}

Your code simply wraps the return value of the event attachment call.

You could wrap the whole thing (connection + task calls), but in your case, I would suggest a different approach:

Right now, you are connecting to Celery every time someone is calling the method. I would advise having a persistent connection to Celery, if possible.

The functions that you are trying to wrap do not adhere to the wrapAsync requirements, so you would have to wrap them in a function that does.

In the following code, both the connection and call functions are taken care of. Note that those functions take a cb argument, which will be provided to them by Meteor, and call it with an error and/or result, as appropriate.

those functions are then passed to wrapAsync.

The sync version of them throws if an error is passed to the callback and simulate a synchronous run using a fiber if they are called in a synchronous manner (i.e, no callback is passed). That's the reason for the try..catch block.

import { Meteor } from 'meteor/meteor';
const celery = require('node-celery'); // or `import` it

function createAndConnectAsync(details, cb) {
    const client = celery.createClient(details);
    const errHandler = function(e) {
        cb(e, client);
    };
    client.once('connect', function() {
        client.off('error', errHandler);
        cb(null, client); // success
    });
    client.once('error', errHandler);
}

function callAsync(client, task, args, cb) {
    client.call(task, args, function(result) {
       cb(null, result);
    });
}

const createAndConnect = Meteor.wrapAsync(createAndConnectAsync);
const call = Meteor.wrapAsync(callAsync);

let client;
try {
   client = createAndConnect({...}); // returns after the client is connected
} catch(e) {
    // connection error
}

Meteor.methods({
    'estimates.request'(data) {
        // generate `name`
        const result = call(client, 'proj.tasks.register', [name]);
        return result;
    }
});

Upvotes: 3

Aage Torleif
Aage Torleif

Reputation: 2013

Combinding async libraries can be tricky, usually they have some helper methods like Meteor's bindEnvironment.

The function returned from Meteor.bindEnvironment also automatically gets run in a Fiber.

Meteor.methods({
    'estimates.request'(data) {
        var celery = require('node-celery')
        var client = celery.createClient({...})

        var otherCallback = Meteor.bindEnvironment(function(err, result) {
            console.log('result: ', result);    // this is not executed
            client.end();
            return result;
        });
        var connectCallback = Meteor.bindEnvironment(function() {
          console.log('connected');                   // this is executed
          client.call('proj.tasks.register', [name], otherCallback);
        });
        client.on('connect', connectCallback);
    }
});

FYI: didn't test this because your example isn't 100% complete :)

Upvotes: 2

Related Questions