-
Notifications
You must be signed in to change notification settings - Fork 5.2k
[Meteor 3] (server-first) Solving Meteor call Async stubValuePromise issue #12907
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Meteor 3] (server-first) Solving Meteor call Async stubValuePromise issue #12907
Conversation
Fixes 'Error: A method named 'echo' is already defined' on Travis
if (Meteor.isClient) | ||
// client can fool itself by cheating, but only until the sync | ||
// finishes | ||
await checkBalances(test, -10, 160); | ||
else await checkBalances(test, 90, 60); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zodern maybe from this test, the api should be stub first, then the server result? because now it needs to await for the server result.
Not sure if this is correct
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you look at the old code for the test:
meteor/packages/ddp-client/test/livedata_tests.js
Lines 377 to 397 in e076317
function(test, expect) { | |
Meteor.call( | |
'ledger/transfer', | |
test.runId(), | |
'alice', | |
'bob', | |
100, | |
true, | |
expect(function(err, result) { | |
failure(test, 409)(err, result); | |
// Balances are reverted back to pre-stub values. | |
checkBalances(test, 90, 60); | |
}) | |
); | |
if (Meteor.isClient) | |
// client can fool itself by cheating, but only until the sync | |
// finishes | |
checkBalances(test, -10, 160); | |
else checkBalances(test, 90, 60); | |
} |
You could maybe implement it for Meteor 3 like this:
let promise = Meteor.callAsync(
'ledger/transfer',
test.runId(),
'alice',
'bob',
100,
true,
);
if (Meteor.isClient) {
// client can fool itself by cheating, but only until the sync
// finishes
await promise.stub
await checkBalances(test, -10, 160);
}
await promise.catch(err => {
failure(test, 409)(err);
});
// Balances are reverted back to pre-stub values.
await checkBalances(test, 90, 60);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I made a mistake somewhere in here
meteor/packages/ddp-client/common/livedata_connection.js
Lines 891 to 907 in fd2150c
// block waiting for the result. | |
if (future) { | |
future.stub = stubReturnValue; | |
future.serverResult = future; | |
if (options.returnServerPromise) { | |
return future; | |
} | |
if (options.returnStubValue) { | |
return future.then(() => stubReturnValue); | |
} | |
return future; | |
} | |
return options.returnStubValue ? stubReturnValue : undefined; | |
} |
The test still does not pass
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took at look at this over the weekend. Unfortunately wasn't able to make the test pass. I noticed that checkBalances
on the client is running before the stub promise has completed. When I added a timeout of 1, prior to checkBalances
it passed.
if (Meteor.isClient) {
// client can fool itself by cheating, but only until the sync
// finishes
await promise.stub // this isn't being awaited
// if i add -- await sleep(1) -- it passes
await checkBalances(test, -10, 160);
}
Hope this can be of help. Maybe someone closer to the codebase will be able to instantly tell what the issue is. 🙂 If I get some time this week, I will take a closer look.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on what @jamauro found, it probably is because promise
is the promise returned by .catch
instead of the promise returned by callAsync
. In the example code I shared, the .catch
is done separately which avoids the issue.
Another issue is the code failure(test, 409)(err);
needs to be after the .catch instead of inside it. Otherwise, the check will never run if the method succeeds.
Will this solution enable getting the stub result before the server promise resolves? It's not clear to me if If not, is there a reason why something like this approach wouldn't work? |
It could work... this is more of a discussion of what the API should look like, and we could have both, as you suggested. This PR focuses on being server-first. because it defaults to be the server result, and now we are async. That test needed to be changed(not sure if I like this). Not sure if I understood you question @jamauro, with this proposal we can do this. const { stub } = Meteor.callAsync(...);
const resultFromStub = await stub; And while coding this example I realize that makes sense as well to have server result as well in this object. |
Ok. I trust that y'all will come up with the best solution. 🙂 I mainly wanted to ensure that I could use the stub result without waiting on the server and then if the server errors, I could rollback the action I took with the stub result. I don't do this often but it can be kind of nice when working with a newly inserted id. |
@jamauro I've added your idea. You can also do this: const { stub, serverResult }= Meteor.callAsync(...);
const whatServerDid = await serverResult;
const whatStubDid = await stub;
// or just
const whatServerDid2 = await Meteor.callAsync(...) |
future.stub = stubReturnValue; | ||
future.serverResult = future; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest renaming these to stubPromise
and serverPromise
for consistency, and to make it clear what the properties contain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I can tell, stubReturnValue
is currently a value and not a promise that is passed into _apply
from applyAsync
. I think the code needs to be changed upstream of future.stubPromise = stubReturnValue
in order for things to work as expected. I could be wrong though. There's a lot going on in this code :)
stubOptions.stubReturnValue = await stubInvocation(); |
Also it's not clear to me why stubInvocation
is being awaited here when it is not a promise:
meteor/packages/ddp-client/common/livedata_connection.js
Lines 972 to 982 in fd2150c
const stubInvocation = () => { | |
if (Meteor.isServer) { | |
// Because saveOriginals and retrieveOriginals aren't reentrant, | |
// don't allow stubs to yield. | |
return Meteor._noYieldsAllowed(() => { | |
// re-clone, so that the stub can't affect our caller's values | |
return stub.apply(invocation, EJSON.clone(args)); | |
}); | |
} else { | |
return stub.apply(invocation, EJSON.clone(args)); | |
} |
This is stretching my developer mind. Every time I see this, I feel something is wrong. Just a quick litmus test: Does the reason for having two interfaces for the result of |
…c: stubPromise and serverPromise
- bring zodern's solution to async stubs
…lpers - fix empty documents tests - fix database error reporting tests
…lpers - fix empty documents tests - fix database error reporting tests
- stop changing how Meteor.apply works
- bring back Meteor.apply in the stub queue
… into feature/solving-meteor-callasync-stubvaluepromise # Conflicts: # packages/ddp-client/client/client_convenience.js # packages/ddp-client/package.js
…valuepromise # Conflicts: # packages/accounts-2fa/.npm/package/npm-shrinkwrap.json # packages/email/.npm/package/npm-shrinkwrap.json # packages/npm-mongo/.npm/package/npm-shrinkwrap.json
- require Groups before Cleaning up
- inject new stub async helpers in the Connection.prototype - adjust tests to work with the new api
As discussed in #12888 and #12897 we need to come to a decision in this API:
For this PR we have implemented the comment here: