Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

Grubba27
Copy link
Contributor

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:

const promise = Meteor.callAsync(...);

const resultFromStub = await promise.stub;
const resultFromServer = await promise;

// backwards compatible with existing code:
const resultFromServer = await Meteor.callAsync(...);

Comment on lines 417 to 421
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);
Copy link
Contributor Author

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

Copy link
Collaborator

@zodern zodern Nov 30, 2023

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:

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);

Copy link
Contributor Author

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

// 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

Copy link
Contributor

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.

Copy link
Collaborator

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.

@jamauro
Copy link
Contributor

jamauro commented Nov 30, 2023

Will this solution enable getting the stub result before the server promise resolves? It's not clear to me if await promise.stub is awaiting the promise and then grabbing the stub or if it's somehow awaiting a stubPromise.

If not, is there a reason why something like this approach wouldn't work?

@Grubba27
Copy link
Contributor Author

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.

@jamauro
Copy link
Contributor

jamauro commented Nov 30, 2023

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.

@Grubba27
Copy link
Contributor Author

@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(...)

@StorytellerCZ StorytellerCZ added this to the Release 3.0 milestone Dec 1, 2023
Comment on lines 893 to 894
future.stub = stubReturnValue;
future.serverResult = future;
Copy link
Collaborator

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.

Copy link
Contributor

@jamauro jamauro Dec 4, 2023

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:

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));
}

@rj-david
Copy link
Contributor

rj-david commented Dec 5, 2023

@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(...)

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 Meteor.callAsync() outweigh all the hardships of explaining why there are two interfaces?

- 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
@denihs denihs merged commit f0b4e16 into release-3.0 Dec 22, 2023
@Grubba27 Grubba27 mentioned this pull request Dec 28, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants