Skip to main content

Calling JavaScript

When running on a JS host (the browser, Node, Bun), Raven can call JavaScript directly – no bindings or glue code. This is the main way Raven programs reach the outside world today: the DOM, npm packages, files, timers.

The js function

js converts Raven objects to JavaScript ones. Use the results largely as you would in JS proper – property access, method calls, new:

> js("hello")
js("hello")
> js("hello").toUpperCase()
js("HELLO")

You can also use js like a namespace; it represents globalThis:

> js.Math.sqrt(5)
js(2.23606797749979)

Calling JS results in (boxed) JS objects, not Raven ones, so you'll usually want to convert back:

> String(js("hello").toUpperCase())
"HELLO"
> Float64(js.Math.sqrt(5))
2.23606797749979

Construct JS objects with new, and load modules with import:

encoder = new(js.TextEncoder)
fs = import("fs/promises")

Inline JavaScript

You can write JavaScript inline with js template strings, interpolating Raven values with \:

fn mysqrt(x) {
result = js`return Math.sqrt(\x)`
return Float64(result)
}

The code is evaluated in a function context, so you need to return to get a value back. Triple-quote templates work for longer snippets:

fn sleep(n) {
p = ```js
return new Promise(resolve => {
setTimeout(() => { resolve() }, \n * 1000)
})
```
await(p)
return
}

Promises, without function colouring

Unlike JS, Raven has no async/await keywords, and no distinction between async and sync functions. We still unwrap JS promises with await, but it's a normal function call – any function can use it, and callers don't know or care:

fn readFile(f: String) {
fs = import("fs/promises")
return String(await(fs.readFile(f, "utf8")))
}

That sleep above is the same story: it blocks its caller for n seconds, whoever that caller is, with no viral async annotations up the call chain. To run something concurrently rather than waiting, wrap it in the async macro, which returns a task:

async {
slowThing()
}

(Concurrency is young – tasks and channels exist but are still settling. See Status.)

Converting data

Conversions are explicit in both directions. js(x) goes out; type constructors (String, Float64, Int32, map(..., UInt8) and so on) come back:

> s = "foo"
> map(new(js.TextEncoder).encode(s), UInt8)
[0x66, 0x6f, 0x6f]
> map(js`return new TextEncoder().encode(\s)`, UInt8)
[0x66, 0x6f, 0x6f]

JS arrays are iterable from Raven, which is why map works on them directly.