Cyclone 3DR Script
From Technodigit, part of Hexagon. Copyright 1997-2026.
Loading...
Searching...
No Matches
Async, Fetch, Import & WebAssembly

Since the latest release, the Cyclone 3DR script engine supports four major new capabilities: asynchronous programming with async/await, HTTP requests with the Fetch API, ES module imports, and WebAssembly execution.

Async / Await

The script engine now fully supports async functions and the await keyword. The engine automatically tracks all pending promises and keeps the script alive until every asynchronous operation has completed.

Basic Usage

// Define an async function
async function loadData()
{
var response = await fetch("https://api.example.com/data");
var data = await response.json();
print("Received: " + data.value);
}
// Call it: the script waits for completion
loadData();

Error Handling

Use standard try/catch blocks inside async functions to handle errors.

async function safeFetch(url)
{
try
{
var response = await fetch(url);
return await response.json();
}
catch (err)
{
print("Request failed: " + err);
return null;
}
}

Parallel Operations

Use Promise.all() to run multiple asynchronous operations in parallel.

async function fetchMultiple()
{
var urls = [
"https://api.example.com/data1",
"https://api.example.com/data2",
"https://api.example.com/data3"
];
// Fetch all URLs in parallel
var responses = await Promise.all(urls.map(function(u) { return fetch(u); }));
var results = await Promise.all(responses.map(function(r) { return r.json(); }));
results.forEach(function(data) {
print("Result: " + data.value);
});
}

Other Promise Methods

The following Promise static methods are all supported:

  • Promise.all(): Resolves when all promises resolve. Rejects if any promise rejects.
  • Promise.race(): Resolves or rejects with the first promise to settle.
  • Promise.allSettled(): Resolves when all promises settle (regardless of success or failure).
  • Promise.resolve() / Promise.reject(): Create already-settled promises.
// Wait for the fastest response
var first = await Promise.race([
fetch("https://server-a.example.com/data"),
fetch("https://server-b.example.com/data")
]);
// Check which operations succeeded or failed
var results = await Promise.allSettled([
fetch("https://reliable-server.example.com/data"),
fetch("https://unreliable-server.example.com/data")
]);
results.forEach(function(r) {
print(r.status); // "fulfilled" or "rejected"
});
Note
You can also use traditional promise chains with .then(), .catch(), and .finally() if you prefer.

Fetch API

Scripts can send HTTP and HTTPS requests with the global fetch() function.

Warning
What the script engine provides is a subset of the Web Fetch API, not a complete implementation. The call syntax and the promise-based behavior are the same as in a browser, but only what is described in this section is available. Everything else is missing or behaves differently: read What Is Not Supported before porting browser code.

Calling fetch()

fetch(url [, options]) starts the request and immediately returns a Promise which resolves with a response object once the whole answer has been received.

Argument Type Description
url string Mandatory. Absolute URL using the http:// or https:// scheme.
options.method string GET (default), POST, PUT, DELETE or HEAD. Lowercase is accepted.
options.headers object Plain object whose properties are the request headers to send.
options.body string Request body. Sent for POST and PUT only.

The url must be a string: passing a Request or a URL object is not supported.
Any other option (mode, credentials, cache, redirect, referrer, integrity, signal, keepalive, ...) is silently ignored.

Warning
Unlike the browser, calling fetch() with a missing or non-string url throws immediately instead of returning a rejected promise. Inside an async function that exception is converted into a rejection and can be caught with try/catch, but in the main body of a script it aborts the execution.

GET Request

var response = await fetch("https://api.example.com/items");
var data = await response.json();
print("Items count: " + data.length);

POST Request

The body must be a string. Serialize your data yourself, typically with JSON.stringify(), and declare the matching Content-Type: no header is added for you.

var response = await fetch("https://api.example.com/items", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer my-token"
},
body: JSON.stringify({ name: "New Item", quantity: 10 })
});
if (response.ok)
{
var result = await response.json();
print("Created item: " + result.id);
}
else
{
print("Error: " + response.status + " " + response.statusText);
}
print(any arg)
Print to output the argument.
Warning
A body which is not a string is converted with the standard JavaScript string conversion, which almost never gives what you expect (an object becomes the text "[object Object]"). Binary request bodies (ArrayBuffer, typed arrays, Blob, FormData, URLSearchParams) are not supported.

Response Object

The whole answer is downloaded before the promise resolves, so the response object below always carries a complete body.

Property / Method Type Description
response.ok boolean true when the HTTP status is in the 200-299 range.
response.status number HTTP status code (200, 404, 500, ...).
response.statusText string HTTP reason phrase ("OK", "Not Found", ...).
response.headers object Response headers as a plain object. See Request and Response Headers.
response.text() Promise Resolves with the body decoded as UTF-8 text.
response.json() Promise Resolves with the body parsed as JSON.
response.arrayBuffer() Promise Resolves with the raw body bytes as an ArrayBuffer, for binary content such as a WebAssembly module.

That list is exhaustive. There is in particular no response.url, response.type, response.redirected, response.bodyUsed, response.body stream, response.clone(), response.blob() nor response.formData().

Note
The response is a plain JavaScript object, not an instance of the Web API Response class. The engine declares no global Response, Request nor Headers constructor, so tests such as response instanceof Response cannot be used. Properties whose name starts with an underscore are internal to the engine: never rely on them.

Since the body is held in memory, text(), json() and arrayBuffer() may be called several times and in any order on the same response. A browser would fail on the second call, so do not rely on this if the same script must also run in a browser.

Warning
Those three methods read the response they are called on: always invoke them as response.text(), response.json() or response.arrayBuffer(). A detached reference such as var read = response.text; read(); does not work.

response.text() always decodes the body as UTF-8, whatever the charset advertised by the server. Other encodings are not converted.
response.json() parses the same UTF-8 text and its promise is rejected when the body is not valid JSON.

Request and Response Headers

Request headers are given as a plain object of string values. The Headers class is not available, so it cannot be used to build them.

var response = await fetch("https://api.example.com/items", {
headers: { "Accept": "application/json", "X-Api-Key": "my-key" }
});

Response headers are exposed as a plain object too, which means the browser accessors response.headers.get(), .has() and .forEach() do not exist. Two consequences are worth remembering:

  • Header names are kept exactly as the server sent them, and property lookup is case sensitive. Compare names in lowercase rather than assuming a spelling.
  • A header sent several times (Set-Cookie is the usual case) only keeps its last value.
// Robust way to read a response header
function getHeader(response, name)
{
var wanted = name.toLowerCase();
var keys = Object.keys(response.headers);
for (var i = 0; i < keys.length; i++)
{
if (keys[i].toLowerCase() === wanted)
return response.headers[keys[i]];
}
return null;
}
print("Type: " + getHeader(response, "content-type"));

Supported HTTP Methods

  • GET (default): Retrieve data.
  • POST: Submit data, with a body.
  • PUT: Update data, with a body.
  • DELETE: Remove data. The body is ignored.
  • HEAD: Retrieve headers only. The response body is empty.
Warning
No other method is implemented: PATCH, OPTIONS, TRACE or a custom verb rejects the promise with the message "fetch(): unsupported HTTP method".
A body is only transmitted with POST and PUT. It is silently dropped for GET, DELETE and HEAD.

Error Handling

Network errors (unknown host, connection refused, aborted transfer, TLS failure) reject the fetch() promise.
HTTP errors (4xx, 5xx) do not: the promise resolves and response.ok is set to false. Always check response.ok or response.status.

try
{
// Network error: the promise rejects
await fetch("https://this.server.does.not.exist/data");
}
catch (err)
{
print("Network error: " + err);
}
// HTTP error: the promise resolves with ok = false
var response = await fetch("https://api.example.com/missing-endpoint");
if (!response.ok)
{
print("HTTP error: " + response.status);
}
Warning
Rejections carry a string, not an Error nor a TypeError object. Printing it or concatenating it works as shown above, but err.message, err.name and err.stack are all undefined.

HTTPS, Redirections and Cancellation

  • HTTPS: TLS 1.2 or later is enforced and the server certificate is verified. A self-signed, expired or mismatched certificate makes the request fail, and there is no option to bypass the verification.
  • Redirections: they are followed automatically, except a redirection which would downgrade HTTPS to HTTP. As response.url does not exist, the final URL cannot be read back from the response.
  • Timeout: no timeout option is exposed. A request which never answers keeps the script waiting.
  • Cancellation: AbortController and options.signal do not exist. Stopping the script from the user interface aborts the requests still in flight, and their promises are rejected.
  • Authentication: there is no credentials option and no interactive credential prompt. Send an Authorization header, or any token your server expects, explicitly.

What Is Not Supported

Summary of the browser features which are not available:

  • The Request, Response, Headers, FormData, Blob and URLSearchParams classes.
  • Every option other than method, headers and body, in particular signal (AbortController), mode, credentials, cache, redirect, referrer, integrity and keepalive.
  • HTTP methods other than GET, POST, PUT, DELETE and HEAD.
  • Non-string request bodies: binary uploads and multipart form data.
  • Streaming: response.body, ReadableStream, and progress notifications while uploading or downloading.
  • response.clone(), response.blob(), response.formData(), response.url, response.type, response.redirected and response.bodyUsed.
  • Body decoding other than UTF-8.
  • Error objects: rejection values are strings.
  • Schemes other than http:// and https://.
Note
No browser origin exists here, so no CORS restriction applies to a script: any reachable server can be called.

Dynamic Import

The import() operator allows you to dynamically load ES modules at runtime.

Importing a Local Module

Create an ES module file (e.g. my_utils.js) with exports:

// File: my_utils.js
export function greet(name) { return "Hello, " + name + "!"; }
export const VERSION = "1.0";

Then import it in your script:

var utils = await import("C:/Scripts/my_utils.js");
print(utils.greet("World")); // "Hello, World!"
print("Version: " + utils.VERSION);

Relative Paths

When your script is saved to a file, you can use relative paths. The import is resolved relative to the current script's location.

// If your script is at C:/Scripts/main.js:
var utils = await import("./my_utils.js"); // C:/Scripts/my_utils.js
var helpers = await import("./lib/helpers.js"); // C:/Scripts/lib/helpers.js
Note
The .js extension is optional. If omitted and the file is not found, the engine automatically appends .js.

Importing from a URL

You can also import ES modules from an HTTP/HTTPS URL:

var mod = await import("https://example.com/scripts/utils.js");
print(mod.someFunction());

Static Imports Between Modules

Inside an ES module file, you can use the standard import ... from syntax to import other modules:

// File: geometry.js
import { PI, multiply } from "./math_utils.js";
export function circumference(radius)
{
return 2 * PI * radius;
}
Note
The import ... from syntax only works inside ES module files. It cannot be used in your main script.
Your main script must use the dynamic import() function to load the first module. That module can then use import ... from to load its own dependencies.
// Main script: uses dynamic import() to load the module
var geo = await import("./geometry.js");
// geometry.js internally uses: import { PI, multiply } from "./math_utils.js"
print("Circumference: " + geo.circumference(10));

This means you can organize your code into multiple module files with dependencies between them, and only the entry point needs to use import().

Module Caching

Modules are cached after the first load. Importing the same module multiple times returns the same instance without re-fetching or re-compiling.

var m1 = await import("./math_utils.js");
var m2 = await import("./math_utils.js"); // Cached, instant return

TypeScript Definitions (.d.ts)

To enable autocompletion for your modules in the built-in script editor, create a TypeScript definition file next to your module:

// File: my_utils.d.ts
export function greet(name: string): string;
export const VERSION: string;

When the module is loaded, the script editor automatically discovers the .d.ts file and provides:

  • Autocomplete suggestions for the module's exported functions and variables.
  • Type hints when hovering over imported symbols.

Error Handling

If a module cannot be found or has syntax errors, the import() promise is rejected.

try
{
await import("./nonexistent_module.js");
}
catch (err)
{
print("Import failed: " + err);
}

WebAssembly

The script engine supports WebAssembly through the standard WebAssembly JavaScript API. This allows scripts to load and execute compiled WebAssembly modules for computationally intensive tasks.

Typical Workflow

The standard workflow is to fetch a .wasm file, convert the response to an ArrayBuffer, and then instantiate it:

// 1. Fetch the WASM binary
var response = await fetch("https://example.com/math.wasm");
var bytes = await response.arrayBuffer();
// 2. Compile and instantiate
var result = await WebAssembly.instantiate(bytes);
// 3. Use exported functions
var add = result.instance.exports.add;
print("10 + 20 = " + add(10, 20));

Validating a Module

You can validate a WASM binary before instantiating it:

var response = await fetch("https://example.com/module.wasm");
var bytes = await response.arrayBuffer();
if (WebAssembly.validate(bytes))
{
var result = await WebAssembly.instantiate(bytes);
// Use the module...
}
else
{
print("Invalid WASM module!");
}

Available API

  • WebAssembly.instantiate(bytes [, imports]): Compile and instantiate a WASM module. Returns a Promise resolving to an object with module and instance properties.
  • WebAssembly.compile(bytes): Compile a WASM module without instantiating it. Returns a Promise resolving to a WebAssembly.Module.
  • WebAssembly.validate(bytes): Synchronously checks if bytes form a valid WASM module. Returns a boolean.

Complete Example: Import + Fetch + Async

Here is a complete example combining all four features:

// Import a local utility module
var math = await import("./modules/math_utils.js");
// Fetch some data from an API
var response = await fetch("https://api.example.com/measurements");
var measurements = await response.json();
// Process data using the imported module
var total = 0;
measurements.forEach(function(m) {
total = math.add(total, m.value);
});
print("Total: " + total);
print("Average: " + (total / measurements.length));
print("Circle area for avg radius: " + math.circleArea(total / measurements.length));
Note
All asynchronous operations (fetch, import, WebAssembly) use the same promise-based mechanism. The script engine automatically waits for all pending operations before the script finishes.