Asynchronous Javascript Requests: Difference between revisions

From RPTools Wiki
Jump to navigation Jump to search
mNo edit summary
 
(3 intermediate revisions by 2 users not shown)
Line 4: Line 4:
The first is a reimplementation of the old {{func|XMLHttpRequest}}. It is unlikely you'll want to use this directly, but it exists to let older javascript libraries function.  Also, the second API is implemented on top of {{func|XMLHttpRequest}}, so if you need lower level access, this is the way to go.
The first is a reimplementation of the old {{func|XMLHttpRequest}}. It is unlikely you'll want to use this directly, but it exists to let older javascript libraries function.  Also, the second API is implemented on top of {{func|XMLHttpRequest}}, so if you need lower level access, this is the way to go.


{{Caution|Be very careful with this!  As of MapTool 1.14.3, all MTscript macros are still running on the Swing thread — the one that updates the GUI.  If an MTscript macro on the Swing thread invokes {{func|frame5}} (or similar), the panel that opens is running on the JavaFX thread instead.  If the JavaFX thread calls MTscript and that macro tries to communicate in any way with the JavaFX thread, you'll likely have MapTool lock up on you.  This is essentially like ''crossing the streams'' so '''DON'T DO THAT'''.}}


Assume the following macro is named <code>myMacro</code> and is on a library token called <code>lib:myLib</code>.
Assume the following macro is named {{code|myMacro}} and is on a library token called {{code|lib:myLib}}.


<syntaxhighlight lang="mtmacro">
<syntaxhighlight lang="mtmacro">
[h: broadcast(macro.args)]
[h: broadcast(macro.args)]
[h: val=1d20]
[h: val = 1d20]
[r: "Arguments: "+macro.args]
[r: "Arguments: " + macro.args]
[r: "d20: "+val]
[r: "d20: " + val]
</syntaxhighlight>
</syntaxhighlight>


The following javascript will execute the macro and spit its result to the chat box.
The following JavaScript will execute the macro and spit its result to the chat box.


<syntaxhighlight lang="js">
<syntaxhighlight lang="js">
function getValue(valueCallback) {
function getValue(valueCallback) {
     let x = new XMLHttpRequest()
     let x = new XMLHttpRequest();
     x.open("POST", "macro:myMacro@lib:myLib", true) // can use false for synchronous requests, but it is discouraged
    // can use `false` for synchronous requests (discouraged)
     x.onreadystatechange = function() { // runs at each change of readystate, we're done when we hit state 4
     x.open("POST", "macro:myMacro@lib:myLib", true);
 
    // runs at each change of readystate
     x.onreadystatechange = function() {
      // readyState == 4 means the request is complete
       if (x.readyState == 4) {
       if (x.readyState == 4) {
         valueCallback(x.response)
         valueCallback(x.response);
       }
       }
     }
     };
     x.send("arguments")
     x.send("arguments");
}
}


Line 31: Line 36:
</syntaxhighlight>
</syntaxhighlight>


Often, you'll want to return structured data from the macro, which can be provided like this:


Often, you'll want to return structured data from the macro, which can be provided like so.
Assume the following macro is named {{code|myJson}} and is on a library token called {{code|lib:myLib}}.
 
Assume the following macro is named <code>myJson</code> and is on a library token called <code>lib:myLib</code>.


<syntaxhighlight lang="mtmacro">
<syntaxhighlight lang="mtmacro">
[h: val=1d20]
[h: val = 1d20]
[h: result = "{}"]
[h: result = "{}"]
[h: result=json.set(result, "target", macro.args)]
[h: result = json.set(result, "target", macro.args)]
[h: result=json.set(result, "value", val)]
[h: result = json.set(result, "value", val)]
[r: result]
[r: result]
</syntaxhighlight>
</syntaxhighlight>


This can easily be retrieved by using the second asynchronous javascript API: {{func|fetch}}
The result can be retrieved by using the second asynchronous JavaScript API: {{func|fetch}}


<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
let x = fetch("macro:myJson@lib:myLib", {method: "POST", body: "someTarget"})
let x = fetch("macro:myJson@lib:myLib", {method: "POST", body: "someTarget"});
x.then((r)=>
x.then((r)=>
   {
   {
     r.json().then((result)=>{
     r.json().then((result)=>{
      console.log(result.value)
        console.log(result.value);
    },
      },
    (error)=>{
      (error)=>{
      console.log("Response not valid json")
        console.error("Response not valid json");
      console.log(error)
        console.error(error);
     })
      }
     )
   },
   },
   (e)=>
   (e)=>
   {
   {
     console.log("macro invocation failed")
     console.error("macro invocation failed");
     console.log(e)
     console.error(e);
   }
   }
)
)
</syntaxhighlight>
</syntaxhighlight>


As usual with javascript, if we are happy with <code>async</code> functions, we can clean that up considerably.  Here is an example fetching the same macro as a string instead of calling it.  (Note that <code>Allow URI access</code> must be enabled on the lib token).
As usual with JavaScript, if we are happy with {{code|async}} functions, we can clean that up considerably.  Here is an example fetching the same macro result as a string (this is a {{code|GET}} request with no parameters).  (Note that {{code|Allow URI access}} must be enabled on the {{code|Lib:}} token).


<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
async function getResyntaxhighlight() {
async function getMacroResult() {
   let r = await fetch("lib://myLib/macro/myJson")
   let r = await fetch("lib://myLib/macro/myJson");
   // We cannot ask for the body as json, as it is just a UTF8 string.
   // We cannot ask for the body as json, as it is just a UTF8 string.
   let body = await r.text()
   let body = await r.text();
   console.log(body)
   console.log(body);
  // If we know it to be JSON, could call `JSON.parse(body)`
}
}
</syntaxhighlight>
</syntaxhighlight>


Note that <code>await</code> will throw an exception if the awaited promise fails.  Use <code>try/catch</code> if there is a risk of the macro failing in some way.
Note that {{code|await}} will throw an exception if the await'ed promise fails.  Use {{code|try/catch}} if there is a risk of the macro failing in some way.
 


[[Category:Cookbook]]
[[Category:Cookbook]][[Category:HTML5 JavaScript]]

Latest revision as of 23:59, 24 January 2024

Languages:  English

There are two APIs for making asynchronous requests from HTML5 javascript to MTScript.

The first is a reimplementation of the old XMLHttpRequest(). It is unlikely you'll want to use this directly, but it exists to let older javascript libraries function. Also, the second API is implemented on top of XMLHttpRequest(), so if you need lower level access, this is the way to go.

Caution:
Be very careful with this! As of MapTool 1.14.3, all MTscript macros are still running on the Swing thread — the one that updates the GUI. If an MTscript macro on the Swing thread invokes frame5() (or similar), the panel that opens is running on the JavaFX thread instead. If the JavaFX thread calls MTscript and that macro tries to communicate in any way with the JavaFX thread, you'll likely have MapTool lock up on you. This is essentially like crossing the streams so DON'T DO THAT.

Assume the following macro is named myMacro and is on a library token called lib:myLib.

[h: broadcast(macro.args)]
[h: val = 1d20]
[r: "Arguments: " + macro.args]
[r: "d20: " + val]

The following JavaScript will execute the macro and spit its result to the chat box.

function getValue(valueCallback) {
    let x = new XMLHttpRequest();
    // can use `false` for synchronous requests (discouraged)
    x.open("POST", "macro:myMacro@lib:myLib", true);

    // runs at each change of readystate
    x.onreadystatechange = function() {
      // readyState == 4 means the request is complete
      if (x.readyState == 4) {
        valueCallback(x.response);
      }
    };
    x.send("arguments");
}

getValue((response)=>{console.log(response)})

Often, you'll want to return structured data from the macro, which can be provided like this:

Assume the following macro is named myJson and is on a library token called lib:myLib.

[h: val = 1d20]
[h: result = "{}"]
[h: result = json.set(result, "target", macro.args)]
[h: result = json.set(result, "value", val)]
[r: result]

The result can be retrieved by using the second asynchronous JavaScript API: fetch()

let x = fetch("macro:myJson@lib:myLib", {method: "POST", body: "someTarget"});
x.then((r)=>
  {
    r.json().then((result)=>{
        console.log(result.value);
      },
      (error)=>{
        console.error("Response not valid json");
        console.error(error);
      }
    )
  },
  (e)=>
  {
    console.error("macro invocation failed");
    console.error(e);
  }
)

As usual with JavaScript, if we are happy with async functions, we can clean that up considerably. Here is an example fetching the same macro result as a string (this is a GET request with no parameters). (Note that Allow URI access must be enabled on the Lib: token).

async function getMacroResult() {
  let r = await fetch("lib://myLib/macro/myJson");
  // We cannot ask for the body as json, as it is just a UTF8 string.
  let body = await r.text();
  console.log(body);
  // If we know it to be JSON, could call `JSON.parse(body)`
}

Note that await will throw an exception if the await'ed promise fails. Use try/catch if there is a risk of the macro failing in some way.