core.performMenuCommand({ commandId }) executed only after all other batchPlays

I have a plugin which depends on selected layers. I also have another plugin, which, after performing some actions, selects specific layers. What I want, is for the second plugin to select layers only after I performMenuCommand() of the first plugin. All batchPlays here are synchronous.

// 2nd plugin actions
batchPlay(actions);
batchPlay([SAVE SELECTED LAYERS ACTIONS]);

batchPlay([SELECT LAYER FOR 1ST PLUGIN ACTION]);
core.performMenuCommand({ [1ST PLUGIN MENU COMMAND ID] });

// If I leave the line bellow, performMenuCommand() always fails 
//   because it's executed only after all batchPlays bellow are done
// But if I comment this line out, menu command is run as expected,
//   but then it wouldn't re-select the layers I need for the second plugin (obviously)
batchPlay([RE-SELECT PREVIOUSLY SAVED LAYERS ACTIONS]);

batchPlay([OTHER FINISHING ACTIONS WITHOUT CHANGING THE SELECTION]);

I see performMenuCommand() is async. Any way to make it synchronous too? Can’t find any docs (again) :frowning:

P. S. Sorry for this [SYNTAX], it wouldn’t let me post because of tooLongWords

Did you try to await it or wrap subsequent code in a .then-block?
According to the prerelease types the function returns a Promise<boolean>.

I’m still very confused about all this async/await stuff :confused:
If I add await, then I have to add async to my whole function and it becomes asynchronous, doesn’t it? Also can’t get it to work with then() :frowning:

function doStuff() {
    async function executeCommand(commandId) {
        return await core.performMenuCommand({commandId});
    }

    executeCommand(-110).then(
        batchPlay([RE-SELECT PREVIOUSLY SAVED LAYERS ACTIONS]);
    );
}

But elsewhere I also have:

doStuff();
batchPlay([OTHER FINISHING ACTIONS WITHOUT CHANGING THE SELECTION]);

And executeCommand() promise is pending until everything is done. And for some reason when executeCommand() is finally done, layers are already re-selected. Looks like then() is performed before async is done :exploding_head:

Correct :+1: Also, if you’re returning a promise, it’s not even necessary to add the await keyword - if you think about it, what do you want to wait for in the scope of this function? return is always the last executed line, so there’s nothing after it. Btw, if you add async to your function, everything you return will already be a promise, which can also tidy up the code. I can really recommend typescript + tslint, it makes things so much easier and literally shows you all those little details:

image
Here, the inferred return type is Promise<number> although I just return a number (because of async)

image
Tslint shows that you can remove await in return statements

image


In regards to your example: I’m still not sure what’s the exact order you want the code to run in. Do you want it to be like that:

  1. performMenuCommand (start)
  2. Other finishing actions
  3. Reselect, once performMenuCommand is finished

?

Basically yes, but would prefer re-select before other finishing actions

async function executeCommand(commandId) {
    return core.performMenuCommand({commandId});
}

async function doStuff() {
    await executeCommand(-110)     
    batchPlay([RE-SELECT PREVIOUSLY SAVED LAYERS ACTIONS]);
}

await doStuff();
batchPlay([OTHER FINISHING ACTIONS WITHOUT CHANGING THE SELECTION]);

Maybe like this?

Also, in my code I always wrote commandID, but if commandId works fine for you this doesn’t seem to be case sensitive.

But again… If I add await doStuff(), then I have to add async all the way to the top function and that makes whole myPluginCommand asynchronous, doesn’t it? Wouldn’t it become something like this?

entrypoints.setup({
    commands: {
        myPluginCommand: async () => await myPluginCommand()
    }
});

Yes, but is that a problem? I’m not sure if the commands object expects any specific function type but I kinda doubt it. And if it does, you could still just call your async function in a synchronous main() function or similar.

Alternatively, you could also do it with then, which doesn’t require the parent function to be async if I remember correctly:

doStuff().then(() => {
    batchPlay([OTHER FINISHING ACTIONS WITHOUT CHANGING THE SELECTION]);
})
1 Like

Ah… It’s a callback. I think that’s what I was missing. Will check again after work :slight_smile: Thanks

1 Like

I also overlooked that in your code example, easy to miss. Good luck!

Spent a couple of hours now, but can’t make it work :frowning: At some point I had it working, but for some reason I got something like

Can’t call then() on undefined

But now can’t even reproduce that :cry:

I now even tried passing the callback all the way up to core.performMenuCommand({commandId}).then(callback), but still performMenuCommand() is executed only after callback is done. WTH?


OK, here’s the full flow… I have batchPlay synchronous

entrypoints.setup({
    commands: {
        mainCommand: () => mainCommand()
    }
});

const mainCommand = function () {
    let result = batchPlay(
        [lots, of, actions],
        historyState
    );

    batchPlay([actions, with, IDs, from, result]);

    runOtherPluginMenu(result[0].layerID);
};

const runOtherPluginMenu = function (id) {
    const allSelectedLayerIds = getSelectedLayersIDs();

    batchPlay([selectLayerWith_id]);

    const cb = () => {
        batchPlay([...reSelect_allSelectedLayerIds]);
    };

    const menuPath = ['menu', 'titles', 'path'];

    /// Here core.performMenuCommand() never gets executed ...
    // const menuItem = getMenuItemByPath(menuPath);
    // core.performMenuCommand(menuItem.command).then(cb);

    // ... But here it gets executed
    // except that core.performMenuCommand() is run only after layers are re-selected 
    //    by running a callback in .then() in executeCommand() function
    runMenuCommand(menuPath, cb);
};

function runMenuCommand(path, callback = null) {
    const menuItem = getMenuItemByPath(menuPath);

    if (menuItem && typeof menuItem.command != 'undefined') {
        executeCommand(menuItem.command, callback);

        return;
    }

    if (typeof callback == 'function') {
        callback();
    }
}

function executeCommand(commandId, callback = null) {
    core.performMenuCommand({commandId}).then(callback);
}

Refactored two functions to this:

function runMenuCommand(path, callback = null) {
    const menuItem = getMenuItemByPath(menuPath);

    if (menuItem && typeof menuItem.command != 'undefined') {
        executeCommand(menuItem.command, callback);
    } else if (typeof callback == 'function') {
        callback();
    }
}

function executeCommand(commandId, callback = null) {
    let p = core.performMenuCommand({commandId});

    p.then(callback);
}

If I break on p.then(callback), I see p is still Pending and only after it executes the callback, the menu item is triggered :confused: Might it be a bug?