jamauro / method

An easy way to create Meteor methods
13 stars 1 forks source link

Bug: Calling method after awaiting something else causes `Called saveOriginals twice without retrieveOriginals` error #11

Closed wildhart closed 1 month ago

wildhart commented 1 month ago

This is a weird bug which occurs when calling a method to insert or upsert a document into a collection after a UI button click but first doing something async.

Steps to reproduce

Packages:

Here's my method - just a simple upsertAsync (but I've also tried insertAsync, and the sync versions with the same error result).

import { createMethod } from 'meteor/jam:method';

export const siteInspectionMethods = {
    upsertJam: createMethod({
        name: 'inspections.upsertJam',
        validate(inspection: SiteInspection) {
            /************************** VALIDATE PERMISSIONS ***********************************/
            check(this.userId, Match.isStaff);
            /************************** VALIDATE INPUTS ****************************************/
            check(inspection._id, Match.stringNotBlank);
            /***********************************************************************************/
        },
        async run(inspection: SiteInspection) {
            console.log('Method: inspections.upsertJam', inspection._id);
            await inspectionsCollection.upsertAsync(inspection._id, inspection);
            return inspection._id;
        },
    }),

And then a simple click handler in the UI:

async onButtonClick() {
    // simulate doing something async:
    await new Promise(resolve => setTimeout(resolve, 100));
    await siteInspectionMethods.upsertJam({_id: Random.id()});
},

Clicking the button causes this error (note that none of these trace lines are in jam:offline or jam:method):

image

If you remove the await new Promise(resolve => setTimeout(resolve, 100)); line, then it works without an error, and the document is upserted correctly.

If I remove jam:method by defining a simple method instead:

Meteor.methods({
    async upsertInspection(inspection: SiteInspection) {
        console.log('Method: upsertInspection', inspection._id);
        await inspectionsCollection.upsertAsync(inspection._id, inspection);
    },
});

Then call this method instead, after the delay:

async onButtonClick() {
    await new Promise(resolve => setTimeout(resolve, 100));
    //await siteInspectionMethods.upsertJam({_id: Random.id()});
    Meteor.call('upsertInspection', {_id: Random.id()});
},

Then it works OK without any error messages.

This also works:

async onButtonClick() {
    setTimeout(() => {
        siteInspectionMethods.upsertJam({_id: Random.id()});
    }, 100);
},

But this FAILS with the same error:

onButtonClick() {
    new Promise(resolve => setTimeout(resolve, 100)).then(() => {
        siteInspectionMethods.upsertJam({_id: Random.id()});
    });
},

Any ideas? Can you repro this?

jamauro commented 1 month ago

I will take a closer look soon but out of curiosity, is this with Meteor 2.16? Can you repro with 2.14 or 3.0.2?

wildhart commented 1 month ago

Yes, this is 2.16. I will try 2.14 later and let you know how I get on...

jamauro commented 1 month ago

I wasn't able to reproduce with async / await syntax.

async onButtonClick() {
    // simulate doing something async:
    await new Promise(resolve => setTimeout(resolve, 100));
    await siteInspectionMethods.upsertJam({_id: Random.id()});
}

I was able to reproduce with

onButtonClick() {
    new Promise(resolve => setTimeout(resolve, 100)).then(() => {
        siteInspectionMethods.upsertJam({_id: Random.id()});
    });
}

And more importantly I was able to reproduce without using jam:method:

onButtonClick() {
    new Promise(resolve => setTimeout(resolve, 100)).then(() => {
        Meteor.callAsync(methodName, ...args)
    });
}

I think this issue lies with Meteor 2.x itself and not this package. I remember a discussion a while back about some limitations with regard to *Async collection methods. I think some (maybe all) of those limitations were captured here: https://github.com/zodern/fix-async-stubs# which led to zodern's solution being incorporated into 3.x

wildhart commented 1 month ago

FYI I was able to reproduce on Meteor 2.14

Installing meteor add zodern:fix-async-stubs seems to resolve the issue, thanks for that. Feel free to close this issue.