- fix livedata stub - methods

- revert callAsync code
This commit is contained in:
denihs
2023-02-08 09:22:04 -04:00
parent 59a30a0fa9
commit 82e3456e5b
3 changed files with 74 additions and 15 deletions

View File

@@ -237,8 +237,8 @@ export class Connection {
// Block auto-reload while we're waiting for method responses.
if (Meteor.isClient &&
Package.reload &&
! options.reloadWithOutstanding) {
Package.reload &&
! options.reloadWithOutstanding) {
Package.reload.Reload._onMigrate(retry => {
if (! self._readyToMigrate()) {
self._retryMigrate = retry;
@@ -523,6 +523,13 @@ export class Connection {
});
}
_getIsSimulation({isFromCallAsync, alreadyInSimulation}) {
if (!isFromCallAsync) {
return alreadyInSimulation;
}
return alreadyInSimulation && DDP._CurrentMethodInvocation._isCallAsyncMethodRunning();
}
/**
* @memberOf Meteor
* @importFromPackage meteor
@@ -560,9 +567,44 @@ export class Connection {
"Meteor.callAsync() does not accept a callback. You should 'await' the result, or use .then()."
);
}
return this.applyAsync(name, args, {
isFromCallAsync: true
/*
* This is necessary because when you call a Promise.then, you're actually calling a bound function by Meteor.
*
* This is done by this code https://github.com/meteor/meteor/blob/17673c66878d3f7b1d564a4215eb0633fa679017/npm-packages/meteor-promise/promise_client.js#L1-L16. (All the logic below can be removed in the future, when we stop overwriting the
* Promise.)
*
* When you call a ".then()", like "Meteor.callAsync().then()", the global context (inside currentValues)
* will be from the call of Meteor.callAsync(), and not the context after the promise is done.
*
* This means that without this code if you call a stub inside the ".then()", this stub will act as a simulation
* and won't reach the server.
*
* Inside the function _getIsSimulation(), if isFromCallAsync is false, we continue to consider just the
* alreadyInSimulation, otherwise, isFromCallAsync is true, we also check the value of callAsyncMethodRunning (by
* calling DDP._CurrentMethodInvocation._isCallAsyncMethodRunning()).
*
* With this, if a stub is running inside a ".then()", it'll know it's not a simulation, because callAsyncMethodRunning
* will be false.
*
* DDP._CurrentMethodInvocation._set() is important because without it, if you have a code like:
*
* Meteor.callAsync("m1").then(() => {
* Meteor.callAsync("m2")
* })
*
* The call the method m2 will act as a simulation and won't reach the server. That's why we reset the context here
* before calling everything else.
*
* */
DDP._CurrentMethodInvocation._set();
DDP._CurrentMethodInvocation._setCallAsyncMethodRunning(true);
return new Promise((resolve, reject) => {
this.applyAsync(name, args, { isFromCallAsync: true })
.then(result => {
DDP._CurrentMethodInvocation._setCallAsyncMethodRunning(false);
resolve(result);
})
.catch(reject);
});
}
@@ -586,7 +628,12 @@ export class Connection {
const { stubInvocation, invocation, ...stubOptions } = this._stubCall(name, EJSON.clone(args));
if (stubOptions.hasStub) {
if (!stubOptions.alreadyInSimulation) {
if (
!this._getIsSimulation({
alreadyInSimulation: stubOptions.alreadyInSimulation,
isFromCallAsync: stubOptions.isFromCallAsync,
})
) {
this._saveOriginals();
}
try {
@@ -617,7 +664,12 @@ export class Connection {
async applyAsync(name, args, options, callback = null) {
const { stubInvocation, invocation, ...stubOptions } = this._stubCall(name, EJSON.clone(args), options);
if (stubOptions.hasStub) {
if (!stubOptions.alreadyInSimulation) {
if (
!this._getIsSimulation({
alreadyInSimulation: stubOptions.alreadyInSimulation,
isFromCallAsync: stubOptions.isFromCallAsync,
})
) {
this._saveOriginals();
}
try {
@@ -680,7 +732,12 @@ export class Connection {
// If we're in a simulation, stop and return the result we have,
// rather than going on to do an RPC. If there was no stub,
// we'll end up returning undefined.
if (stubCallValue.isFromCallAsync) {
if (
this._getIsSimulation({
alreadyInSimulation,
isFromCallAsync: stubCallValue.isFromCallAsync,
})
) {
if (callback) {
callback(exception, stubReturnValue);
return undefined;

View File

@@ -315,13 +315,13 @@ Tinytest.addAsync(
);
Meteor.methods({
testResolvedPromise(arg) {
const invocation1 = DDP._CurrentMethodInvocation.get();
async testResolvedPromise(arg) {
const invocationRunningFromCallAsync1 = DDP._CurrentMethodInvocation._isCallAsyncMethodRunning();
return Promise.resolve(arg).then(result => {
const invocation2 = DDP._CurrentMethodInvocation.get();
// This equality holds because Promise callbacks are bound to the
// dynamic environment where .then was called.
if (invocation1 !== invocation2) {
const invocationRunningFromCallAsync2 = DDP._CurrentMethodInvocation._isCallAsyncMethodRunning();
// What matters here is that both invocations are coming from the same call,
// so both of them can be considered a simulation.
if (invocationRunningFromCallAsync1 !== invocationRunningFromCallAsync2) {
throw new Meteor.Error("invocation mismatch");
}
return result + " after waiting";

View File

@@ -20,7 +20,9 @@ export default class LocalCollection {
// _id -> document (also containing id)
this._docs = new LocalCollection._IdMap;
this._observeQueue = new Meteor._AsynchronousQueue();
this._observeQueue = Meteor.isClient
? new Meteor._SynchronousQueue()
: new Meteor._AsynchronousQueue();
this.next_qid = 1; // live query id generator