TextDecoder[1] API 是一个用于将二进制数据(通常是 ArrayBuffer 或 TypedArray)解码为字符串的 JavaScript API。它是 Web 平台的一部分,主要用于处理文本编码的解码工作。比如,从服务器接收到的流式数据、文件数据等。
在处理 Web 应用中的二进制数据时,通常需要将这些数据转换为可读的字符串格式。TextDecoder 提供了一种高效且便捷的方法来实现这一点。
TextDecoder API 有以下特点:
下面我们将介绍 TextDecoder API 的四种使用场景:
// 创建一个包含 UTF-8 编码文本的 Uint8Arrayconst uint8Array = new Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]);// 创建一个 TextDecoder 实例,默认使用 UTF-8 编码const decoder = new TextDecoder('utf-8');// 将 Uint8Array 解码为字符串const decodedString = decoder.decode(uint8Array);console.log(decodedString); // 输出 "Hello World"
// 使用不同的编码创建 TextDecoder 实例const utf16Decoder = new TextDecoder('utf-16');const iso88591Decoder = new TextDecoder('iso-8859-1');// 示例二进制数据const utf16Array = new Uint16Array([0x0048, 0x0065, 0x006C, 0x006C, 0x006F]);const iso88591Array = new Uint8Array([72, 101, 108, 108, 111]);// 解码为字符串const utf16String = utf16Decoder.decode(utf16Array);const iso88591String = iso88591Decoder.decode(iso88591Array);console.log(utf16String); // 输出 "Hello"console.log(iso88591String); // 输出 "Hello"
首先,我们先来看一下效果:
图片
在以上的示例中,我们使用 Node.js 的 http 模块,快速搭建一个本地 SSE[2](Server-Sent Events)服务器。
server.js
const http = require("http");const PORT = 3000;const server = http.createServer((req, res) => { if (req.url === "/sse") { res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept", }); let id = 1; const interval = setInterval(() => { const data = { id: id, message: `This is message ${id}`, timestamp: +new Date(), }; res.write(`data: ${JSON.stringify(data)}/n/n`); // 停止条件 if (id == 5) { res.write("event: end/n"); res.write("data: End of stream/n/n"); clearInterval(interval); res.end(); } id++; }, 1000); req.on("close", () => { clearInterval(interval); }); } else { res.writeHead(404, { "Content-Type": "text/plain" }); res.end("404 Not Found"); }});server.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`);});
需要注意的是,在 sse 接口中,我们设置 Content-Type 响应头的类型为: "text/event-stream",告诉客户端我们返回的是流式数据。
index.html
⚠️:访问 index.html 网页前,记得运行 node server.js 命令先启动 SSE 服务器。
<!-- public/index.html --><!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>SSE & TextDecoder</title></head><body> <h1>Decode Server-Sent Events JSON Stream Data</h1> <div id="messages"></div> <script src="client.js"></script></body></html>
client.js
document.addEventListener("DOMContentLoaded", () => { const messagesDiv = document.querySelector("#messages"); const textDecoder = new TextDecoder("utf-8"); fetch("http://localhost:3000/sse").then((response) => { const reader = response.body.getReader(); return new ReadableStream({ start(controller) { function push() { reader.read().then(({ done, value }) => { if (done) { controller.close(); return; } const chunk = textDecoder.decode(value, { stream: true }); const lines = chunk.split("/n"); for (const line of lines) { if (line.startsWith("data: ")) { const json = line.slice(6); const data = JSON.parse(json); const p = document.createElement("p"); p.textContent = `ID: ${data.id}, Message: ${data.message}, Timestamp: ${data.timestamp}`; messagesDiv.appendChild(p); } else if (line.startsWith("event: end")) { const p = document.createElement("p"); p.textContent = "End of stream"; messagesDiv.appendChild(p); return; } } push(); }); } push(); }, }); });});
SSE 事件流是一个简单的文本数据流,文本是使用 UTF-8 格式的编码。所以,在创建 textDecoder 对象时,我们需要设置它的编码为 utf-8。有了 textDecoder 对象后,调用它提供的 decode 方法就可以进行解码了。
当遇到传输大 JSON 数据的时候,如果使用传统的方式,我们需要接收完整的 JSON 数据后,才能开始进行数据处理,这对用户体验会造成一定的影响。为了解决这个问题,我们可以使用一些现成的 JSON Stream 解析库。比如,@streamparser/json[3],该库内部也使用了本文介绍的 TextDecoder API。
下面,我们先来看一下效果:
上图中输出的 JSON 数据来自以下的 large.json 文件。我们对该文件按照 0.5KB 的大小进行切割,然后每个 500ms 发送下一个数据块。利用 @streamparser/json 这个库,我们实现了解析 JSON 数据流(JSON Chunk)的功能。
large.json
[ {}, { "image": [ { "shape": "rect", "fill": "#333", "stroke": "#999", "x": 0.5e1, "y": 0.5, "z": 0.8, "w": 0.5e5, "u": 2e10, "foo": 2e1, "bar": 2, "width": 47, "height": 47 } ], "corners": { "1": true, "3": true, "7": true, "9": true } }, ...]
json-server.js
const http = require("http");const { join } = require("path");const { readFileSync } = require("fs");const PORT = 3000;const largeJson = readFileSync(join(__dirname, "large.json")).toString();const server = http.createServer((req, res) => { if (req.url === "/stream-json") { res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-cache", Connection: "keep-alive", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept", }); const CHUNK_SIZE = 512; // 每块 0.5KB let position = 0; const interval = setInterval(() => { const chunk = largeJson.slice(position, position + CHUNK_SIZE); res.write(chunk); position += CHUNK_SIZE; if (position >= largeJson.length) { clearInterval(interval); res.end(); } }, 500); // 每 500ms 发送一块数据 req.on("close", () => { clearInterval(interval); }); } else { res.writeHead(404, { "Content-Type": "text/plain" }); res.end("404 Not Found"); }});server.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`);});
stream-json.html
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Stream JSON</title> </head> <body> <h1>Stream JSON</h1> <div id="messages"></div> <script type="module"> import { JSONParser } from "https://cdn.jsdelivr.net/npm/@streamparser/json-whatwg@0.0.21/+esm"; const messagesDiv = document.querySelector("#messages"); document.addEventListener("DOMContentLoaded", async () => { const parser = new JSONParser(); const response = await fetch("http://localhost:3000/stream-json"); const reader = response.body.pipeThrough(parser).getReader(); while (true) { const { done, value: parsedElementInfo } = await reader.read(); if (done) break; const { value, key, parent, stack, partial } = parsedElementInfo; if (partial) { console.log(`Parsing value: ${value}... (still parsing)`); } else { const p = document.createElement("p"); p.textContent = `${JSON.stringify(value)}`; messagesDiv.appendChild(p); console.log(`Value parsed: ${JSON.stringify(value)}`); } } }); </script> </body></html>
@streamparser/json 这个库还有其他的用法,如果你感兴趣的话,可以看一下它的使用文档。
参考资料
[1]TextDecoder: https://developer.mozilla.org/zh-CN/docs/Web/API/TextDecoder
[2]SSE: https://developer.mozilla.org/zh-CN/docs/Web/API/Server-sent_events
[3]@streamparser/json: https://www.npmjs.com/package/@streamparser/json
本文链接:http://www.28at.com/showinfo-26-88708-0.html高效传输大的 JSON 数据,流式处理真香!
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com