⚠️

Gotchas

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:

image

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 Promises, which is part of why we automatically await & unwrap Promises 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.

Promises aren’t always where you expect them to be

Val Town tries to await(unwrap) Promises 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
  • LinkedIn

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.