问题
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