HTML Prompt: Wait Until window.KeyboardMaestro.Trigger('macro') Completes

Hi,

I need to trigger a macro in the HTML Prompt javascript and then retrieve the result for further process.
It looks like window.KeyboardMaestro.Trigger('macro'); is async, and I don't know how to wait until the macro completes.
Can anyone show me how?

Thanks!

There's a 50% chance I'm wrong, so wait for others to reply, but I thought Javascript worked in a sandbox so that it couldn't read your file system. If Javascript could do what you want it to do (access KM to do anything you want), you would be breaking that sandbox. I googled this and found a couple of websites that seem to confirm what I'm saying, but I'm in way over my head here.

You're right javascript (not node.js) cannot read local files, but with Keyboard Maestro Custom HTML Prompt, we can use javascript to trigger Keyboard Maestro macros, which can read local files.

What I'm trying to do here is

  1. to use javascript to set a KM variable and trigger another macro, which uses the variable, do something with local files, return a value.
  2. javascript then gets the returned variable value to do other things in the HTML Prompt.

I need to wait until the macro completes before continuing step 2.

Ok. I was way over my head. I didn't appreciate that distinction. If you think I merit any punishment for being wrong, let me know what I should do.

oh, no.
I appreciate your help.
If I did not learn from your answer, you might learn something from my reply. Haha~

I'm not sure if there's a better/offical way, but couldn't you just set a variable at the end of the macro (which runs asynchronously) and wait for that variable's new value before your Javascript continues?

That's something I was looking for.
I could make the javascript to check, say every 0.2s, to see if the macro has ended. But I'm wondering if there is a better way to do that.

KM10 added window.KeyboardMaestro.ProcessAppleScript( 'tell app "Finder" to activate' ). I'm not sure if we could implement it for my purpose. I tried window.KeyboardMaestro.ProcessAppleScript( 'tell application "Keyboard Maestro Engine" to do script "93007888-2620-43B1-B100-57612D88A34D"' ) and it did not even trigger the macro.

There's only two ways to check a value... repeated polling and responding to signals/triggers. I have no idea if there's a trigger from KM to JS. I'm basically just a sounding board for you to explain yourself and maybe figure it out via your explanations. :slight_smile:

1 Like

That's not a feature of the language per se – JavaScript embedded in a browser can't see the the file-system, but the JavaScript for Automation embedding, for example, has a library through which the whole system is accessible.

1 Like

I always appreciate being corrected or clarified. Thanks.

martin,

It can be something like that.

As sleepy say, you can compare by value... but I do not know what is exactly your need.

So I just this code out, it should be something like the following, comparing by timestamp before retrieving data to ensure accuracy (because same data may not mean data is accurate)

Once the timestamp is different, then the data is retrieved.
You need to create the macro to do the processing and myApp_funcResult to original timestamp
You decide whether to handle the reject, I leave it out.

const taskTimeStamp = new Date().getTime();
  KM?.trigger("yourMacro", JSON.stringify({
    command: "getData", taskTimeStamp,
    kmReturnVarName: "myApp_funcResult", parameter: {
      myName: "david" // Define whatever parameter
    }
  }));
  
  function getKmVariable(kmReturnVarName, taskTimeStamp, interval = 500) {
    return new Promise((resolve, reject) => {
  
      const intervalId = setInterval(() => {
          // kmReturnVarName variable myApp_funcResult originally set to have taskTimeStamp
          // while macro done, it change the timeStamp of myApp_funcResult to current time to
          // indicate it is completed.
          const varValue = KM?.GetVariable(kmReturnVarName);
          const {timeStamp, data} = JSON.parse(varValue);
  
          if (timeStamp !== taskTimeStamp) {
            clearInterval(intervalId);
            resolve(data);
          }
        }
        , interval);
    });
  }
  
  // Getting the value ==================
  
  getKmVariable("myApp_funcResult",
    taskTimeStamp)
    .then(data => {
      console.log(data);
    })
    
  
  // or  async await
  
  (async () => {
    const data = await getKmVariable("myApp_funcResult",
      taskTimeStamp);
    console.log(data);
  })();

If you want, you can also check for variable existence just as Sleepy suggest
"
"I'm not sure if there's a better/offical way, but couldn't you just set a variable at the end of the macro (which runs asynchronously) and wait for that variable's new value before your Javascript continues?"

"
Sleepy's simple solution is better.

ensure that global variable is empty,
and then poll until global variable has data. I remember having do something along this line before , so may not need timestamp. Just make sure that global variable is used specifically for that macro.

set global variable "myApp_returnValue" to empty
triggerMacro
use JS promise to check if variable exists (to prevent blocking), if exists, that is the data
clear the global variable.

Hi @macdevign_mac,
Thanks for your proposed solution.

This is exactly what I'm doing right now.

I set the FlagCheck variable at the beginning of the macro:

I also added the action at the end of the macro to tell my javascript that the macro has completed:

In the javascript of my HTML Prompt, I use a sleep function for pause:

        function sleep(milliseconds) {
            const date = Date.now();
            let currentDate = null;
            do {
                currentDate = Date.now();
            } while (currentDate - date < milliseconds);
        }

Then use a while function to determine whether the macro has completed or not. If completed, then return the macro result (the value of variable txt):

        function triggerQuery(str) {
            window.KeyboardMaestro.SetVariable('sql', str);
            window.KeyboardMaestro.Trigger('target_macro'); // query database
            let flag = 'a';
            while (flag !== 'b') {
                sleep(200);
                flag = window.KeyboardMaestro.GetVariable('FlagValue');
            }
            return window.KeyboardMaestro.GetVariable('txt');
        }

@Sleepy,

Thanks a lot for your suggestions!

You are most welcome. I think I was a bit lucky today, because anytime I talk about JavaScript, it's not really from experience.

1 Like

I'm not sure if anyone else mentioned this or not. You could have the macro use "Execute a JavaScript in a Custom HTML Prompt" to tell the prompt that the macro has set the variable.

So rather than have the prompt wait in a loop for the variable to be set, it would trigger the macro and not do anything else.

When the macro sets the variable, it can execute a Javascript function in your Custom HTML Prompt, like this:

image

And in your "variableHasBeenSet" function, you can read the variable's value, then do whatever you were going to do with it. It would be nice if you could actually pass the variable's value in this way, but I can't find a way to do it.

I hope this makes sense. If not, let me know.

1 Like

Hi @DanThomas,

Thanks for pointing that out. I've used this action before, but this time, it did not come up to my mind at all.
This makes perfect sense! I'm gonna mark it as a solution.

1 Like