Search code examples
javascriptnode.jsmeteornpmnpm-request

using Request npm module synchronous in Meteor 1.3


I try to use request npm package in Meteor 1.3.2.4 as synchronous.

Based on this Meteor guide article, first I try to use Meteor.bindEnvironment like below:

    import request from 'request';

    let result = {success: false, error: null, content: ""};
    let requestOptions = {
        url: <MY-URL-HERE>
    };

    request(requestOptions, Meteor.bindEnvironment((error, response, html) => {
        if (!error && response.statusCode == 200) {
            result.success = true;
            result.content = html;
            result.error = null;
        }
        else {
            result.success = false;
            result.content = "";
            result.error = error;
        }
    }));

But it seems still async call.

In next step I try to use Meteor.wrapAsync based on this answer on meteor forum, this is next try code:

        import request from 'request';

        let result = {success: false, error: null, content: ""};
        let requestOptions = {
            url: <MY-URL-HERE>
        };

        let func = function (options, callback) {
            request(options, function (error, response, body) {
                console.log("error: " + JSON.stringify(error));
                console.log("response: " + JSON.stringify(response));
                console.log("body: " + JSON.stringify(body));
                callback(error, {response, body});
            });
        };

        let {error, response, body} = syncRequest(requestOptions);

         console.log("error2: " + JSON.stringify(error));
         console.log("response2: " + JSON.stringify(response));
         console.log("body2: " + JSON.stringify(body));

        if (response.statusCode == 200) {
            result.success = true;
            result.content = body;
            result.error = null;
        }
        else {
            result.success = false;
            result.content = "";
            result.error = error;
        }

This code works fine except request contain error. When request contain error (for example url is incorrect) this break with below exception:

I20160518-08:24:22.180(4.5)? error: {}
I20160518-08:24:22.181(4.5)? response: undefined
I20160518-08:24:22.182(4.5)? body: undefined
W20160518-08:24:22.839(4.5)? (STDERR) TypeError: The header content contains invalid characters
W20160518-08:24:22.839(4.5)? (STDERR)     at Object.Future.wait (/home/cyc/.meteor/packages/meteor-tool/.1.3.2_4.7bk6xv++os.linux.x86_64+web.browser+web.cordova/mt-os.linux.x86_64/dev_bundle/server-lib/node_modules/fibers/future.js:420:15)
W20160518-08:24:22.839(4.5)? (STDERR)     at packages/meteor/helpers.js:119:1
W20160518-08:24:22.839(4.5)? (STDERR)     at Object.getContent (server/lib/get_content.js:51:26)
W20160518-08:24:22.839(4.5)? (STDERR)     at Object.fetch (server/lib/get_content.js:205:27)
W20160518-08:24:22.839(4.5)? (STDERR)     at Object.fetchSource (server/lib/get_content.js:369:31)
W20160518-08:24:22.840(4.5)? (STDERR)     at [object Object].action (server/controller/postsController.js:79:39)
W20160518-08:24:22.840(4.5)? (STDERR)     at [object Object].handle (packages/iron_middleware-stack/lib/handler.js:74:1)
W20160518-08:24:22.840(4.5)? (STDERR)     at boundNext (packages/iron_middleware-stack/lib/middleware_stack.js:251:1)
W20160518-08:24:22.840(4.5)? (STDERR)     at runWithEnvironment (packages/meteor/dynamics_nodejs.js:110:1)
W20160518-08:24:22.840(4.5)? (STDERR)     at packages/meteor/dynamics_nodejs.js:123:1
W20160518-08:24:22.840(4.5)? (STDERR)     - - - - -
W20160518-08:24:22.840(4.5)? (STDERR)     at ClientRequest.OutgoingMessage.setHeader (http.js:733:13)
W20160518-08:24:22.840(4.5)? (STDERR)     at new ClientRequest (http.js:1429:14)
W20160518-08:24:22.840(4.5)? (STDERR)     at Object.exports.request (http.js:1899:10)
W20160518-08:24:22.841(4.5)? (STDERR)     at Request.start (/home/cyc/Programming/Projects/content/Sources/node_modules/request/request.js:753:32)
W20160518-08:24:22.841(4.5)? (STDERR)     at Request.end (/home/cyc/Programming/Projects/content/Sources/node_modules/request/request.js:1418:10)
W20160518-08:24:22.841(4.5)? (STDERR)     at end (/home/cyc/Programming/Projects/content/Sources/node_modules/request/request.js:580:14)
W20160518-08:24:22.841(4.5)? (STDERR)     at Object._onImmediate (/home/cyc/Programming/Projects/content/Sources/node_modules/request/request.js:594:7)
W20160518-08:24:22.841(4.5)? (STDERR)     at processImmediate [as _immediateCallback] (timers.js:363:15)

Now I have two question:

  1. How to fix above code?
  2. Is this approach is the best way to use request npm module synchronous in Meteor or you know better way?

Solution

  • binding the environment

    Meteor.bindEnvironment simply makes sure that your callback is called in the context you were using when calling the original function. It does not supposed to make your code appear synchronous. If you did not use it, your callback will not be run inside a fiber (which will prevent you from performing some actions) and you won't have access to certain variables (such as the current user). There is an excellent video by Crhis Mather on the subject (keep in mind that some of the code is a bit outdated, but the core is still valid).

    wrapping an async function

    Meteor.wrapAsync wraps functions that expect a callback with the standard error-first 2 arguments signature. Since the request does not adhere to this exact standard (it uses a callback with 3 arguments), the forum post suggested wrapping it with a function that expects a callback with the aforementioned 2 arguments.

    The convention is that the result of calling a "wrapped" function is the value of the second argument passed to the callback (the data), and an error is thrown if there is one.

    Therefore, as it is now, you can wrap the function with a try..catch block and see if there is an error.

    Another option is to never raise an error:

    let func = function (options, callback) {
      request(options, function (error, response, body) {
        callback(null, {response, body, error});
      });
    };
    

    Which will always result in a {response, body, error} object, where error will be null if there is no error.

    using the http package

    I think that the http package is perfectly valid and does pretty much the same for you with convenience methods (e.g, the general call, and specific get and post):

    For example:

    import { HTTP } from 'meteor/http';
    const result = HTTP.get('https://my-url.com');