Meteor - Wrapping NPMs with Meteor.wrapAsync()

余生颓废 提交于 2020-01-04 14:17:22

问题


I'm trying to wrap the superagent NPM with Meteor.wrapAsync, everything works fine until the last line of the code below, which causes my meteor app to crash.

var superagent = Meteor.npmRequire('superagent');

// Example of how superagent works
superagent.get('http://127.0.0.1:8080/json/', function(result){
    console.log(result); // Works, shows the result
});

// This appears to work too
var agentAsync = Meteor.wrapAsync(superagent.get);

// This crashes app
agentAsync('http://127.0.0.1:8080/json/');

I've also tried passing a context to wrapAsync() and it makes no difference:

var agentAsync = Meteor.wrapAsync(superagent.get, superagent);

Here is the console output:

W20141124-17:31:32.094(0)? (STDERR)           
W20141124-17:31:32.136(0)? (STDERR) /home/ciwolsey/.meteor/packages/meteor-tool/.1.0.35.1bjny7b++os.linux.x86_64+web.browser+web.cordova/meteor-tool-os.linux.x86_64/dev_bundle/lib/node_modules/fibers/future.js:206
W20141124-17:31:32.136(0)? (STDERR)                         throw(ex);
W20141124-17:31:32.137(0)? (STDERR)                               ^
W20141124-17:31:32.137(0)? (STDERR) [object Object]
W20141124-17:31:32.137(0)? (STDERR)     at Object.Future.wait (/home/ciwolsey/.meteor/packages/meteor-tool/.1.0.35.1bjny7b++os.linux.x86_64+web.browser+web.cordova/meteor-tool-os.linux.x86_64/dev_bundle/lib/node_modules/fibers/future.js:326:15)
W20141124-17:31:32.137(0)? (STDERR)     at packages/meteor/helpers.js:118
W20141124-17:31:32.137(0)? (STDERR)     at app/server/main.js:5:1
W20141124-17:31:32.137(0)? (STDERR)     at app/server/main.js:8:3
W20141124-17:31:32.137(0)? (STDERR)     at /home/ciwolsey/projects/hello/.meteor/local/build/programs/server/boot.js:168:10
W20141124-17:31:32.138(0)? (STDERR)     at Array.forEach (native)
W20141124-17:31:32.138(0)? (STDERR)     at Function._.each._.forEach (/home/ciwolsey/.meteor/packages/meteor-tool/.1.0.35.1bjny7b++os.linux.x86_64+web.browser+web.cordova/meteor-tool-os.linux.x86_64/dev_bundle/lib/node_modules/underscore/underscore.js:79:11)
W20141124-17:31:32.138(0)? (STDERR)     at /home/ciwolsey/projects/hello/.meteor/local/build/programs/server/boot.js:82:5
=> Exited with code: 8

回答1:


Here's the source to Meteor.wrapAsync and the source to superget.get

Meteor.wrapAsync is basically a thin wrapper around Meteor.bindEnviroment. It provides a bound function which waits on a Fiber.

superget.get eventually tries to call the callback function passed to it with Request.prototype.callback

What's interesting here is that Meteor.bindEnvironment takes the Fibers.resolver function (which takes two arguments), and wraps it in a function that takes no arguments.

So when Request.prototype.callback tries to look at fn.length to see if it should call it with (err, res) or send the error off with emit... it does the latter..

To make this work, we need to short-circuit Request.prototype.callback and make it think that functions with no arguments are fine to call as fn(err, res)

superget.Request.prototype.callback = function(err, res){
  var fn = this._callback;
  if (2 == fn.length || 0 == fn.length) return fn(err, res);
  if (err) return this.emit('error', err);
  fn(res);
};

Alternatively, you could write your own Meteor.wrapAsync that provides a callback with the right function length. eg:

function wrapAsync(fn, context) {
  //XXX Shortened version of wrapAsync. Only works on server, doesn't allow for callback to be passed.
  return function (/* arguments */) {
    var self = context || this;
    var newArgs = _.toArray(arguments);
    var fut = new Future();
    var callback = Meteor.bindEnvironment(fut.resolver());
    newArgs.push(function(err, res){
      return callback.apply(this, arguments);
    });
    fn.apply(self, newArgs);
    return fut.wait()
  };
}



回答2:


Meteor.wrapAsync takes a second argument which is the context the wrapped function should be called with (to preserve this correct value).

Try using this syntax instead :

var agentAsync = Meteor.wrapAsync(superagent.get, superagent);

If you don't pass the correct context, the call will crash your app because it won't be able to fetch this properties as it should normally do when you call superagent.get directly.




回答3:


Have you tried using Future? Here's an example

Meteor.methods({
  syncMethod: function() {
    // load Future
    Future = Npm.require('fibers/future');
    var theFuture = new Future();

    // call the function and store its result
    TheAsyncFuncWeWantItToWait("foo", function (error,results){
      if(error){
        theFuture.throw(error);
      }else{
        theFuture.return(results);
      }
    });

    return theFuture.wait();
  }
});



回答4:


I know this is an old question, but I didn't see a clear answer here so I wanted to share what worked for me.

const request = superagent
  .post(`${basePath}/api/xxx`)
  .set('Content-Type', 'application/json')
  .send({ fileReference });
const response = Meteor.wrapAsync(request.end, request)();

Since request.end() is the function that expects a callback, this is what you want to pass into Meteor.wrapAsync. And you have to bind the callback to the original request, otherwise it runs in the global context (but it needs to run in the context of the original request).

Hopefully this helps someone else!



来源:https://stackoverflow.com/questions/27109164/meteor-wrapping-npms-with-meteor-wrapasync

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!