JavaScrip, fetch 和JSON

您可能希望通过在不重新加载整个页面的情况下更改数据来使您的HTML页面动态化。而不是提交一个HTML <form> 并执行重定向以重新呈现模板,您可以添加 JavaScript 这就要求 fetch() 并替换页面上的内容。

fetch() 是一种用于从页面发出请求的现代、内置的JavaScript解决方案。您可能听说过其他“AJAX”方法和库,例如 XMLHttpRequest()jQuery 。现代浏览器不再需要它们,尽管您可以根据应用程序的要求选择使用它们或其他库。这些文档将只关注内置的JavaScript功能。

渲染模板

理解模板和Java脚本之间的区别很重要。在将响应发送到用户的浏览器之前,在服务器上呈现模板。在呈现和发送模板之后,在用户的浏览器中运行JavaScript。因此,不可能使用JavaScript来影响JJJA模板的呈现方式,但是可以将数据呈现到将要运行的Java脚本中。

若要在呈现模板时向JavaScript提供数据,请使用 tojson() 过滤器中的 <script> 阻止。这会将数据转换为有效的JavaScript对象,并确保安全地呈现任何不安全的HTML字符。如果您不使用 tojson 筛选器,您将获得一个 SyntaxError 在浏览器控制台中。

data = generate_report()
return render_template("report.html", chart_data=data)
<script>
    const chart_data = {{ chart_data|tojson }}
    chartLib.makeChart(chart_data)
</script>

一种不太常见的模式是将数据添加到 data- 属性添加到HTML标记上。在这种情况下,必须使用单引号将值引起来,而不是使用双引号,否则将生成无效或不安全的HTML。

<div data-chart='{{ chart_data|tojson }}'></div>

正在生成URL

另一种将数据从服务器获取到JavaScript的方法是发出请求。首先,您需要知道要请求的URL。

生成URL的最简单方法是继续使用 url_for() 在呈现模板时。例如:

const user_url = {{ url_for("user", id=current_user.id)|tojson }}
fetch(user_url).then(...)

但是,您可能需要基于仅在JavaScript中知道的信息来生成URL。如上所述,JavaScript在用户的浏览器中运行,而不是作为模板呈现的一部分,因此您不能使用 url_for 在那一刻。

在这种情况下,您需要知道应用程序在其下提供服务的“根URL”。在简单的设置中,这是 / ,但它也可能是其他东西,比如 https://example.com/myapp/

告诉您的JavaScript代码有关该根的一种简单方法是在呈现模板时将其设置为全局变量。然后,您可以在从JavaScript生成URL时使用它。

const SCRIPT_ROOT = {{ request.script_root|tojson }}
let user_id = ...  // do something to get a user id from the page
let user_url = `${SCRIPT_ROOT}/user/${user_id}`
fetch(user_url).then(...)

通过以下方式提出请求 fetch

fetch() 接受两个参数,一个URL和一个带有其他选项的对象,并返回 Promise. 我们不会讨论所有可用选项,而只会使用 then() 在承诺上,而不是其他回调或 await 语法。有关这些功能的更多信息,请阅读链接的MDN文档。

默认情况下,使用Get方法。如果响应包含JSON,则可以将其与 then() 回调链。

const room_url = {{ url_for("room_detail", id=room.id)|tojson }}
fetch(room_url)
    .then(response => response.json())
    .then(data => {
        // data is a parsed JSON object
    })

若要发送数据,请使用POST等数据方法,并将 body 选择。最常见的数据类型是表单数据或JSON数据。

若要发送表单数据,请传递已填充的 FormData 对象。它使用与HTML表单相同的格式,并可通过 request.form 在烧瓶视图中。

let data = new FormData()
data.append("name", "Flask Room")
data.append("description", "Talk about Flask here.")
fetch(room_url, {
    "method": "POST",
    "body": data,
}).then(...)

通常,更喜欢将请求数据作为表单数据发送,就像在提交HTML表单时使用的那样。JSON可以表示更复杂的数据,但除非需要,否则最好使用更简单的格式。在发送JSON数据时, Content-Type: application/json 还必须发送报头,否则Flask会返回400错误。

let data = {
    "name": "Flask Room",
    "description": "Talk about Flask here.",
}
fetch(room_url, {
    "method": "POST",
    "headers": {"Content-Type": "application/json"},
    "body": JSON.stringify(data),
}).then(...)

以下重定向

响应可能是重定向,例如,如果您使用JavaScript而不是传统的HTML表单登录,并且您的视图返回重定向而不是JSON。JavaScript请求确实遵循重定向,但它们不会更改页面。如果要更改页面,可以检查响应并手动应用重定向。

fetch("/login", {"body": ...}).then(
    response => {
        if (response.redirected) {
            window.location = response.url
        } else {
            showLoginError()
        }
    }
)

替换内容

响应可能是新的HTML,或者是要添加或替换的页面的新部分,或者是一个全新的页面。通常,如果要返回整个页面,最好使用上一节所示的重定向来处理。下面的示例演示如何将 <div> 其中包含由请求返回的HTML。

<div id="geology-fact">
    {{ include "geology_fact.html" }}
</div>
<script>
    const geology_url = {{ url_for("geology_fact")|tojson }}
    const geology_div = getElementById("geology-fact")
    fetch(geology_url)
        .then(response => response.text)
        .then(text => geology_div.innerHTML = text)
</script>

从视图返回JSON

要从API视图返回JSON对象,可以直接从视图返回DICT。它将被自动序列化为JSON。

@app.route("/user/<int:id>")
def user_detail(id):
    user = User.query.get_or_404(id)
    return {
        "username": User.username,
        "email": User.email,
        "picture": url_for("static", filename=f"users/{id}/profile.png"),
    }

如果要返回另一个JSON类型,请使用 jsonify() 函数,该函数创建一个响应对象,该对象将给定的数据序列化为JSON。

from flask import jsonify

@app.route("/users")
def user_list():
    users = User.query.order_by(User.name).all()
    return jsonify([u.to_json() for u in users])

在JSON响应中返回文件数据通常不是一个好主意。JSON不能直接表示二进制数据,因此它必须是Base64编码的,这可能会很慢,需要更多的带宽来发送,并且不容易缓存。相反,使用一个视图提供文件,并生成要包含在JSON中的所需文件的URL。然后,在获得JSON后,客户端可以单独请求获取链接的资源。

在视图中接收JSON

使用 json 属性的属性 request 对象将请求的正文解码为JSON。如果正文不是有效的JSON,或者 Content-Type 标头未设置为 application/json ,则会引发400错误请求错误。

from flask import request

@app.post("/user/<int:id>")
def user_update(id):
    user = User.query.get_or_404(id)
    user.update_from_json(request.json)
    db.session.commit()
    return user.to_json()