- Vals can only have one top-level statement
- Importing
- Only dynamic imports (no static imports)
- default vs named imports
- Rendering
- Only JSON.stringify data is persisted
- Promises aren’t always where you expect them to be
- Concurrency
- Uncaught Async Errors (Unhandled Promise Rejections)
- Forbidden returned from fetch (fetch is proxied)
- No Dynamic @references
- Unexpected Empty objects: {}
Vals can only have one top-level statement
You can only have a single-top level binding in a Val. If you have more than one statement in your code at the top-level, we will automatically wrap your code in an IIFE. For example, when you run this:
It becomes this:
You can then reference @me.untitled_RfqtWbJz
in other Vals and it will refer to 3
.
Importing
Only dynamic imports (no static imports)
We currently don’t allow static imports. You can only import dynamically:
import { foo } from 'bar' // 🚫
const { foo } = await import('bar') // ✅
If you try to use static imports, we will do our best to rewrite it for you.
default
vs named imports
If the library you’re uses a default export, you will need to unwrap it off of default
:
You’ll often notice that you need to do this if you don’t do it and get an unhelpful error:
This isn’t necessary for libraries that use named exports. There’s even a version of lodash that does this:
Rendering
Vals don’t support outputting a UI, such as HTML or JSX.
The most common pattern is to use our ExpressJS API to output HTML:
You can easily import any val into a frontend via the API. Like in this example in Observable.
Only JSON.stringify
data is persisted
A val is (currently) persisted by us calling JSON.stringify
on it, which leaves many properties unpersisted. This is particularly unhelpful for dynamic objects like Promise
s, which is part of why we automatically await & unwrap Promise
s at the top level. We may get better at persisting dynamic objects, maybe even closures one day, but for now, assume that only simple JSON data is persisted.
There are many JS objects with properties that don’t show up via JSON.stringify
. For example, fetch headers are a strange object, so if you naively return them you’ll be disappointed:
You can turn them into a serializable object like so:
In future, we plan to do more of this kind of serialization behind the scenes for you automatically.
Promise
s aren’t always where you expect them to be
Val Town tries to await
(unwrap) Promise
s for you wherever possible, including in nested scenarios:
However, this will get you in trouble if you try to call .then
on this value in another val:
Happily, you will be fine if you try to await
something that’s not actually a Promise
:
In summary, avoid using .then
between vals. On the bright side, you don’t need it. In future we may give the best of both worlds: automatically unwrapping vals, while still allowing you to call .then
on them if you’d like.
Concurrency
There are currently no supported methods for controlled state concurrency. If you have two vals that set the same piece of state at the same time, they may clobber each other. Try to design around this issue for now.
In the future, we may allow atomic changes like setState
does in React, where you define your update to state as a function to the previous state, which would prevent clobberings. If you have other suggestions, we’re all 👂s!
Uncaught Async Errors (Unhandled Promise Rejections)
If you don't await
or .catch
a Promise, any potential errors it throws will not be associated with your val and you won't be notified of it.
// ✅ errors & emails you
try { await fetch("hi") }
catch (e) { console.email(e) }
// ⛔ fails silently
(() => { fetch("hi") })()
// ✅ error in UI
// val town awaits top level Promises
fetch("hi")
Forbidden
returned from fetch
(fetch
is proxied)
We proxy all our requests through a IP randomizer service that has some limitations. It blocks access to:
- Banking and other financial activities (anything related to financial institutions and cryptocurrency financing)
- Government sites
- Entertainment (e.g., Netflix)
- Apple/Google stores
- Ticketing
- Gaming
- Mailing
- Streaming
No Dynamic @references
While you can refer to @me.foo
, you cannot do @me['foo']
. More importantly, you cannot do @me[name]
, where name
is a dynamic variable. This is because we pull all @references
statically, by parsing your code before running it for patterns that match @me.foo
.
Unexpected Empty objects: {}
In many cases, if you’re getting an empty object {}
where you don’t expect one to be, it might be that Promise
that wasn’t awaited. If you get an empty object, try awaiting it and see if that solves the problem. It could also be due to how we only persist JSON data.