Permalink
Browse files

Allow server-side method implementations to return a Promise.

This does not mean that Meteor.call or Meteor.apply now return a Promise.
Completion of the method call is merely delayed until the Promise is
resolved or rejected, at which point the calling code asynchronously
receives the resulting value or exception.

These changes were inspired by this forum thread:
https://forums.meteor.com/t/fibers-and-meteor-promise-npm-pacakge/6531/7
  • Loading branch information...
benjamn committed Aug 17, 2015
1 parent 69509ba commit f5821c88eee587706eb8107f74de2f60c267807f
@@ -675,7 +675,7 @@ _.extend(Session.prototype, {
randomSeed: randomSeed
});

try {
const promise = new Promise((resolve, reject) => {
// XXX It'd be better if we could hook into method handlers better but
// for now, we need to check if the ddp-rate-limiter exists since we
// have a weak requirement for the ddp-rate-limiter package to be added
@@ -692,34 +692,51 @@ _.extend(Session.prototype, {
DDPRateLimiter._increment(rateLimiterInput);
var rateLimitResult = DDPRateLimiter._check(rateLimiterInput)
if (!rateLimitResult.allowed) {
throw new Meteor.Error(
reject(new Meteor.Error(
"too-many-requests",
DDPRateLimiter.getErrorMessage(rateLimitResult),
{timeToReset: rateLimitResult.timeToReset});
{timeToReset: rateLimitResult.timeToReset}
));
return;
}
}

var result = DDPServer._CurrentWriteFence.withValue(fence, function () {
return DDP._CurrentInvocation.withValue(invocation, function () {
return maybeAuditArgumentChecks(
handler, invocation, msg.params, "call to '" + msg.method + "'");
});
});
} catch (e) {
var exception = e;
}
resolve(DDPServer._CurrentWriteFence.withValue(
fence,
() => DDP._CurrentInvocation.withValue(
invocation,
() => maybeAuditArgumentChecks(
handler, invocation, msg.params,
"call to '" + msg.method + "'"
)
)
));
});

fence.arm(); // we're done adding writes to the fence
unblock(); // unblock, if the method hasn't done it already
function finish() {
fence.arm();
unblock();
}

exception = wrapInternalException(
exception, "while invoking method '" + msg.method + "'");
const payload = {
msg: "result",
id: msg.id
};

// send response and add to cache
var payload =
exception ? {error: exception} : (result !== undefined ?
{result: result} : {});
self.send(_.extend({msg: 'result', id: msg.id}, payload));
promise.then((result) => {
finish();
if (result !== undefined) {
payload.result = result;
}
self.send(payload);
}, (exception) => {
finish();
payload.error = wrapInternalException(
exception,
`while invoking method '${msg.method}'`
);
self.send(payload);
});
}
},

@@ -156,3 +156,59 @@ Tinytest.addAsync(
);
}
);

Meteor.methods({
testResolvedPromise(arg) {
const invocation1 = DDP._CurrentInvocation.get();
return new Promise.resolve(arg).then(result => {
const invocation2 = DDP._CurrentInvocation.get();
// This equality holds because Promise callbacks are bound to the
// dynamic environment where .then was called.
if (invocation1 !== invocation2) {
throw new Meteor.Error("invocation mismatch");
}
return result + " after waiting";
});
},

testRejectedPromise(arg) {
return new Promise.resolve(arg).then(result => {
throw new Meteor.Error(result + " raised Meteor.Error");
});
}
});

Tinytest.addAsync(
"livedata server - waiting for Promise",
(test, onComplete) => makeTestConnection(test, (clientConn, serverConn) => {
test.equal(
clientConn.call("testResolvedPromise", "clientConn.call"),
"clientConn.call after waiting"
);

const clientCallPromise = new Promise(
(resolve, reject) => clientConn.call(
"testResolvedPromise",
"clientConn.call with callback",
(error, result) => error ? reject(error) : resolve(result)
)
);

const clientCallRejectedPromise = new Promise(resolve => {
clientConn.call(
"testRejectedPromise",
"with callback",
(error, result) => resolve(error.message)
);
});

Promise.all([
clientCallPromise,
clientCallRejectedPromise,
]).then(results => test.equal(results, [
"clientConn.call with callback after waiting",
"[with callback raised Meteor.Error]",
]), error => test.fail(error))
.then(onComplete);
})
);
@@ -11,7 +11,7 @@ Npm.depends({

Package.onUse(function (api) {
api.use(['check', 'random', 'ejson', 'underscore',
'retry', 'mongo-id', 'diff-sequence'],
'retry', 'mongo-id', 'diff-sequence', 'ecmascript'],
'server');

// common functionality
@@ -53,6 +53,7 @@ Package.onUse(function (api) {


Package.onTest(function (api) {
api.use('ecmascript', ['client', 'server']);
api.use('livedata', ['client', 'server']);
api.use('mongo', ['client', 'server']);
api.use('test-helpers', ['client', 'server']);

0 comments on commit f5821c8

Please sign in to comment.