Language
Async data
Load data from templates with async functions.
Templates can load their own data by calling async functions you provide. A page can fetch what it needs without the server code knowing about every page.
{% set posts = documents("post", limit: 2) %}
<h2>Latest posts</h2>
{% for post in posts %}
<a href="{{ post.url }}">{{ post.title }}</a>
{% endfor %}
<h2>Latest posts</h2>
<a href="/blog/hello-world/">Hello, world</a>
<a href="/blog/layouts/">Layouts in Kiln</a>
Here, documents is a function the host registered. It returns a promise, and Kiln waits for it before carrying on. See Functions, filters and tests to register your own.
How Kiln waits
- Any function, filter or test can return a promise.
- Kiln only waits where a call returns one. Everything else runs straight through, so a template with no async calls never waits.
- Output always follows the order of the template, whatever order the promises finish in. So does content added to an outlet.
- Values in the render data and globals are not awaited, and Kiln never calls their
thenmethod, even next to a call that waits, as in{{ later() ~ value }}. Pass data that is ready, or a function the template can call.
Running at the same time
While one part of a template waits, Kiln starts the parts after it that don't depend on it. Their calls run at the same time, so a page takes about as long as its slowest chain of calls, not all of them added up.
{% set posts = documents("post", limit: 10) %}
{% for post in posts %}
{% set author = document("author", id: post.authorId) %}
<a href="{{ post.url }}">{{ post.title }}</a> by {{ author.name }}
{% endfor %}
{% for category in categories() %}
<a href="{{ category.url }}">{{ category.name }}</a>
{% endfor %}
Here Kiln loads the posts and the categories at the same time. Once the posts arrive, it loads all ten authors at the same time too. That is two waits instead of twelve.
- A part depends on an earlier one when it reads or sets a variable the earlier one sets, or sets a variable the earlier one reads. It waits for that part to finish first.
- A block or slot can read any variable, so it waits for earlier parts that set one, and later parts that set one wait for it. If the content of any block or fill in the page sets a variable, a block or slot also waits for earlier parts that read one, and later parts that read one wait for it.
- A page's top-level
settags don't hold up its layout. While they wait, the parts of the layout that don't use their variables start, such as a header that loads its own menu. Blocks wait for them. - The items of a loop run at the same time when the loop body only sets variables that don't exist outside the loop. Otherwise each item waits for the one before it.
- Outlets,
flushand islands wait for every part before them, so they behave as if nothing ran early. - Up to 64 parts of a render run early at once. Beyond that, parts wait their turn.
- The output is the same as if every part had waited its turn. The limits count the same work too.
Async in filters
map and filter run async arrow functions for every item at once and keep the original order. reduce runs them one at a time. sort needs synchronous arrow functions.
The 64-part limit applies to template statements and loop passes, not calls within an expression or callbacks in map and filter. For example, mapping 1,000 ids can start 1,000 calls before any finish. Your functions must limit access to databases and external APIs, using a shared connection pool or queue. Share that limit across renders when they use the same service. Kiln's step limits count work; they do not cap concurrent network requests.
{% set authors = post.authorIds|map(id => document("author", id: id)) %}
Errors
If a promise rejects, the render stops with a render error. Its cause holds the original error. When parts were running at the same time, Kiln waits for all of them to finish, then stops with the error that comes first in the template.
You can use this on purpose. For example, a notFound() function can throw an error your server catches and turns into a 404 page.
{% set page = document("page", slug: route.path) ?? notFound() %}