理想是火,点燃熄灭的灯。
1.返回头中返回“content-length”字段,前端用于计算进度
2.后端返回的content-type 需要支持以下格式
在前端使用 ReadableStream
来实时获取进度时,支持流传输(streaming)的 Content-Type
是关键。大多数情况下,以下几种 Content-Type
是常用并且支持流传输的:
Content-Type
image/jpeg
image/png
image/gif
video/mp4
video/webm
video/ogg
audio/mpeg
audio/ogg
audio/wav
text/plain
text/html
text/css
text/javascript
application/octet-stream
application/pdf
application/zip
application/json
application/xml
application/x-www-form-urlencoded
Content-Type
application/x-tar
application/gzip
application/x-7z-compressed
application/x-iso9660-image
application/octet-stream
application/x-msdownload
application/vnd.ms-excel
(XLS)application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
(XLSX)application/msword
(DOC)application/vnd.openxmlformats-officedocument.wordprocessingml.document
(DOCX)application/vnd.ms-powerpoint
(PPT)application/vnd.openxmlformats-officedocument.presentationml.presentation
(PPTX)application/x-matroska
(MKV) - 这个格式虽然是视频文件,但它的内部结构使得逐块处理变得复杂。这些格式通常不支持流传输的原因包括:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Download Progress</title> </head> <body> <h1>Download Image with Progress</h1> <progress id="progress-bar" value="0" max="100"></progress> <span id="progress-percent">0%</span> <br /> <img id="downloaded-image" alt="Downloaded Image" style="display: none" /> <button onclick="downloadFile()">下载</button> <script> async function downloadFile(url) { const response = await fetch(url); // 发起网络请求获取文件 const contentLength = response.headers.get("content-length"); // 获取文件的总长度 if (!contentLength) { console.error("Content-Length response header unavailable"); return; } const total = parseInt(contentLength, 10); // 将文件总长度转换为整数 let loaded = 0; // 获取 ReadableStream 的 reader 对象 const reader = response.body.getReader(); // 创建一个新的可读流,用于处理从后端读取的数据 const stream = new ReadableStream({ // start 方法在流启动时调用,接受一个 controller 参数用于控制流的行为 start(controller) { // push 函数用于从 reader 中读取数据并处理 function push() { // 调用 reader.read() 方法读取下一个数据块 reader .read() .then(({ done, value }) => { // 如果 done 为 true,表示没有更多数据可读,流结束 if (done) { controller.close(); // 关闭流 return; // 退出 push 函数 } // 打印当前读取的数据块(仅用于调试) console.log("value_", value); // 累加已读取的数据长度 loaded += value.length; // 计算下载进度百分比 const progress = (loaded / total) * 100; // 更新进度条和进度百分比显示 document.getElementById("progress-bar").value = progress; document.getElementById( "progress-percent" ).textContent = `${progress.toFixed(2)}%`; // 将读取的数据块传给流的消费端 controller.enqueue(value); // 递归调用 push 函数,继续读取下一个数据块 push(); }) .catch((error) => { // 捕获读取错误并在控制台打印 console.error("Read error:", error); controller.error(error); // 报告流的错误 }); } // 启动递归读取数据块 push(); }, }); // 将流转换为 Blob 对象 const responseStream = new Response(stream); const blob = await responseStream.blob(); const urlBlob = URL.createObjectURL(blob); // 创建 Blob URL // 获取用于显示下载图片的 img 元素 const img = document.getElementById("downloaded-image"); img.src = urlBlob; img.style.display = "block"; } // 调用 downloadFile 函数下载文件 // downloadFile("https://biaoblog.cn:5000/uploads/1661914638476.jpg"); </script> </body> </html>
async function downloadResources(link) { let date = Date.now(); return new Promise((resolve) => { const path = Path.resolve(__dirname, "files", `${date}.mp4`); const writer = Fs.createWriteStream(path); writeLog(`当前写入文件名:${date}.mp4`); axios({ url: link, method: "GET", responseType: "stream", onDownloadProgress: function (progressEvent) { let process = ((progressEvent.loaded / progressEvent.total) * 100) | 0; let downloadSpeed = filterByte(progressEvent.rate); let progressText = `下载进度:${process}%`; const logText = `文件总大小:${filterByte( progressEvent.total )},已下载:${filterByte( progressEvent.loaded )},${progressText},当前下载速度: ${downloadSpeed}/s,预计还需要:${formatterSecond( progressEvent.estimated )}`; console.log(logText); writeLog(logText); if (process == 100) { console.log(`当前资源下载完成, 当前写入文件名:${date}.mp4,下载完成`); writeLog(`当前资源下载完成, 当前写入文件名:${date}.mp4,下载完成`); resolve(); } }, }) .then((res) => { res.data.pipe(writer); }) .catch((err) => { writeLog("downloadResources失败:", err); resolve(); }); }); }
`ReadableStream` 和 `EventStream`(通常指的是 Server-Sent Events, SSE)是前端和后端处理流数据的不同技术,它们各自有不同的用途和实现方式。下面是它们的主要区别:
### 1. **定义和用途**
- **`ReadableStream`**:
- **定义**:`ReadableStream` 是浏览器提供的一种流式接口,允许开发者以流式方式处理数据。例如,可以从一个网络请求中逐步读取数据块而不是一次性读取整个数据。
- **用途**:用于处理从浏览器中发出的请求的响应数据,或用于处理浏览器端的数据流,例如文件上传和下载、实时数据处理等。
- **`EventStream`** (Server-Sent Events, SSE):
- **定义**:Server-Sent Events 是一种标准的 HTTP 协议扩展,允许服务器推送更新到浏览器客户端。SSE 通过持久的 HTTP 连接向客户端推送事件数据。
- **用途**:用于在浏览器中接收从服务器推送的实时事件流,例如实时通知、消息更新等。
### 2. **实现方式**
- **`ReadableStream`**:
- **浏览器端**:使用 JavaScript 中的 `ReadableStream` 对象来创建和处理流。可以通过 `fetch` API 获取响应流,并逐步读取数据块。
- **示例**:
const stream = new ReadableStream({ start(controller) { // 假设 reader 是从 fetch 请求中获取的 ReadableStreamDefaultReader function push() { reader.read().then(({ done, value }) => { if (done) { controller.close(); return; } controller.enqueue(value); push(); }); } push(); } });
- **`EventStream`** (SSE):
- **服务器端**:服务器发送 `Content-Type: text/event-stream` 的响应,通过长连接向客户端推送事件。
- **浏览器端**:使用 `EventSource` 对象来接收从服务器推送的事件。
- **示例**:
- **服务器端**(例如 Node.js):
const express = require('express'); const app = express(); app.get('/events', (req, res) => { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); // 发送事件 setInterval(() => { res.write(`data: ${new Date().toISOString()}\n\n`); }, 1000); }); app.listen(3000); ``` - **浏览器端**: ```javascript const eventSource = new EventSource('http://localhost:3000/events'); eventSource.onmessage = function(event) { console.log('New message:', event.data); };
### 3. **数据传输**
- **`ReadableStream`**:
- **数据传输**:处理数据块的流式传输。数据是以块的形式逐步读取和处理的。
- **特性**:允许精细控制数据的读取和处理,可以处理大文件或需要逐步处理的数据流。
- **`EventStream`** (SSE):
- **数据传输**:推送事件流。数据以事件的形式从服务器推送到客户端。
- **特性**:适用于服务器主动推送更新到客户端的场景,如实时通知或数据更新。
### 4. **连接管理**
- **`ReadableStream`**:
- **连接管理**:依赖于标准的 HTTP 请求-响应模式。每次请求都是独立的,数据流在请求生命周期内进行处理。
- **`EventStream`** (SSE):
- **连接管理**:使用持久的 HTTP 连接,服务器可以在连接上不断推送事件,直到客户端断开连接。
### 5. **兼容性和支持**
- **`ReadableStream`**:
- **浏览器支持**:现代浏览器支持 `ReadableStream`。但对于旧版浏览器,可能需要 polyfill 或替代方案。
- **`EventStream`** (SSE):
- **浏览器支持**:大部分现代浏览器支持 SSE,但某些浏览器(如 IE)不支持。
### 总结
- **`ReadableStream`** 主要用于处理流式数据的读取和操作,适用于需要逐步读取和处理数据的场景。
- **`EventStream`** (SSE) 主要用于服务器向客户端推送实时事件,适用于需要实时更新的应用场景。
选择使用哪种技术取决于你的具体需求和应用场景。
EventStream
(Server-Sent Events, SSE)和 WebSockets 都用于实时通信,但它们的工作原理、用途和特性有明显不同。以下是它们之间的主要区别:
EventStream
(Server-Sent Events, SSE):EventStream
(SSE):Content-Type: text/event-stream
来维持持久的连接。ws://
或 wss://
开头。EventStream
(SSE):data:
开头。每个事件用两个换行符分隔。// 服务器端 res.write('data: ' + JSON.stringify({ message: 'Hello!' }) + '\n\n');
// 服务器端(Node.js 示例) const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8080 }); wss.on('connection', ws => { ws.on('message', message => { console.log(`Received message => ${message}`); ws.send('Hello!'); // 发送消息给客户端 }); });
EventStream
(SSE):EventSource
API 处理 SSE。EventStream
(SSE):EventStream
(SSE):EventStream
(SSE) 适用于从服务器向客户端推送事件的场景,提供单向的、基于 HTTP 的实时数据传输。根据你的应用需求选择合适的技术,可以更好地满足性能要求和用户体验。
前端的 ReadableStream
、EventStream
(Server-Sent Events, SSE)、和 WebSocket
都有不同的使用场景,根据你的需求选择合适的技术可以提高应用的性能和用户体验。下面是每种技术的使用场景及其原因:
ReadableStream
ReadableStream
可以逐步处理数据,避免一次性加载整个文件,减少内存占用。Response
对象、fetch
API 等一起使用,实现复杂的数据处理需求。fetch('large-file-url') .then(response => { const reader = response.body.getReader(); const stream = new ReadableStream({ start(controller) { function push() { reader.read().then(({ done, value }) => { if (done) { controller.close(); return; } // 处理数据块 controller.enqueue(value); push(); }); } push(); } }); return new Response(stream); }) .then(response => response.blob()) .then(blob => { // 处理 blob 数据 });
EventStream
(SSE)EventSource
API。const eventSource = new EventSource('http://example.com/events'); eventSource.onmessage = function(event) { console.log('New message:', event.data); }; eventSource.onerror = function(error) { console.error('EventSource error:', error); };
const socket = new WebSocket('ws://example.com/socket'); socket.onopen = function() { console.log('WebSocket connection established'); socket.send('Hello Server!'); }; socket.onmessage = function(event) { console.log('Message from server:', event.data); }; socket.onerror = function(error) { console.error('WebSocket error:', error); };
ReadableStream
:用于处理逐块读取的数据流,适合大文件下载/上传、实时数据处理。EventStream
(SSE):用于从服务器向客户端推送实时事件数据,适合实时通知和动态数据更新。选择合适的技术要根据你的应用需求、数据传输模式以及对实时性的要求来决定。
fetch
在请求到结果之后,默认返回的是一个 Response
对象。这个 Response
对象包含了 HTTP 响应的所有元数据,包括状态码、头部信息等,并且有一个 body
属性表示响应体的数据流。
在没有格式化的情况下,响应体的数据流 (response.body
) 是一个 ReadableStream
。你需要手动读取这个流并进行处理。这个流是原始的字节流,并没有具体的格式,具体的内容格式取决于服务器返回的内容。
为了更清楚地理解,下面是一个 fetch
请求返回的默认 Response
对象结构的示例:
fetch('https://api.example.com/data') .then(response => { // 默认返回的 Response 对象 console.log(response); // Response 对象的结构 /* Response { body: ReadableStream, // 原始的字节流 bodyUsed: false, // 表示 body 是否已经被使用 headers: Headers, // 响应的头部信息 ok: true, // 请求是否成功 redirected: false, // 请求是否重定向 status: 200, // HTTP 状态码 statusText: "OK", // HTTP 状态文本 type: "cors", // 响应类型 (basic, cors, error, opaque, opaqueredirect) url: "https://api.example.com/data" // 请求的 URL } */ // 示例:获取响应的头部信息 console.log(response.headers.get('content-type')); // 示例:获取响应体的 ReadableStream const reader = response.body.getReader(); // 手动读取数据流 reader.read().then(({ done, value }) => { if (!done) { console.log('Received chunk:', value); } }); }) .catch(error => { console.error('Fetch error:', error); });
在这个示例中,response.body
是一个 ReadableStream
对象,表示响应体的原始字节流。在没有进行格式化之前,它没有具体的数据格式,你可以使用各种方法(如 json()
, text()
, blob()
, arrayBuffer()
, formData()
)来解析这个流的内容。
所以,总结来说:
fetch
请求到结果之后默认返回的是一个 Response
对象。Response
对象包含响应的元数据和一个 ReadableStream
(response.body
)表示响应体的原始字节流。json()
, text()
, blob()
等)来解析响应体的内容。结论就是如果要使用readableStream来处理流,在fetch之后不需要格式化,而是需要处理它原始的response,获取其中的body 即:原始的字节流
参考文档:https://developer.mozilla.org/zh-CN/docs/Web/API/ReadableStream/ReadableStream
作者: Bill 本文地址: http://biaoblog.cn/info?id=1722216125715
版权声明: 本文为原创文章,版权归 biaoblog 个人博客 所有,欢迎分享本文,转载请保留出处,谢谢!