6: Async
现在让投票变得即时。Phlo 的前端运行时拦截标记为 async 的表单和链接,在后台发送它们,并将服务器的 DOM 命令应用到页面上。无需重新加载,无需客户端状态,无需 API 层:同一路由同时响应两个世界。
6.1: Switch on the frontend
Add two resources to data/app.json:
{
"resources": [
"DB/DB",
"DB/model",
"DB/JSONDB",
"DB/JSON.result",
"payload",
"phlo.async",
"DOM/form"
]
}
phlo.async is the async request engine in www/app.js (which view() already includes on every page), and DOM/form teaches it to submit forms. A reload regenerates the bundle on the request; the CLI shows it explicitly:
php www/app.php build::run
The output lists *app.js: the frontend bundle was regenerated.
6.2: 路由有同步侧和异步侧
每个 route 默认是仅同步的,除非你另有说明:
- 无修饰符:仅常规 HTTP 请求
async:仅来自 Phlo 前端的请求both:两者皆可
将投票 route 更改为同时服务两种请求,并根据请求类型进行分支:
route both POST poll vote $id {
if (!$option = type_poll::record(id: (int)$id)) return false
type_poll::change('id=?', (int)$id, votes: $option->votes + 1)
if (%req->async) return apply(
outer: ['#results' => $this->results],
)
location('/poll')
}
当 Phlo 前端发起请求时,%req->async 为 true。Async 请求会得到一个 apply(...) 响应;普通浏览器仍会得到重定向。一个 route,两种传输,零重复。
注意 apply(...) 内部的尾随逗号:在 Phlo 中,每个多行参数列表的行都以逗号结尾,包括最后一行。这个逗号告诉解析器语句仍在继续。
6.3: Mark the form as async
The frontend only intercepts elements with the async class. One character in the choices view:
view choices:
<section.card>
<foreach type_poll::records() AS $option>
<form.async method=post action="/poll/vote/$option->id">
<button>$option->option</button>
</form>
</foreach>
</section>
<form.async ...> is the dot shorthand for class="async". The same works for navigation: <a.async href="/poll"> loads a page through the async pipeline with a view transition instead of a full reload.
Reload http://localhost/poll once (to pick up the new markup), then vote. The bar animates to its new width, the counts update, and the page never reloads. The transition: width .4s from chapter 4 is doing the easing.
6.4: apply() 实际上发送了什么
apply() 返回 JSON 命令,前端在 DOM 上执行这些命令。看看它是如何发生的:
curl -s -X POST -H "X-Requested-With: phlo" http://localhost/poll/vote/1
{"outer": {"#results": "<section id=\"results\" class=\"card\">..."}}
outer 替换元素的 outerHTML;服务器渲染了 results view 并将其作为字符串发送。其他命令的工作方式相同:inner、append、remove、class、value、title、path、scroll 等,都是一个 apply() 调用的命名参数。命令名称没有构建时检查,因此像 innr: 这样的拼写错误会被静默忽略;请随时参考指南中的命令表。
从两个不同的浏览器标签页投票,你会发现最后一个间隙:另一个标签页在你重新加载之前不会移动。记住这个想法,留到第 8 章。首先:更多语言。