



理想是火,点燃熄灭的灯。



1.返回头中返回“content-length”字段,前端用于计算进度
2.后端返回的content-type 需要支持以下格式
在前端使用 ReadableStream 来实时获取进度时,支持流传输(streaming)的 Content-Type 是关键。大多数情况下,以下几种 Content-Type 是常用并且支持流传输的:
Content-Typeimage/jpegimage/pngimage/gifvideo/mp4video/webmvideo/oggaudio/mpegaudio/oggaudio/wavtext/plaintext/htmltext/csstext/javascriptapplication/octet-streamapplication/pdfapplication/zipapplication/jsonapplication/xmlapplication/x-www-form-urlencodedContent-Typeapplication/x-tarapplication/gzipapplication/x-7z-compressedapplication/x-iso9660-imageapplication/octet-streamapplication/x-msdownloadapplication/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 都有不同的使用场景,根据你的需求选择合适的技术可以提高应用的性能和用户体验。下面是每种技术的使用场景及其原因:
ReadableStreamReadableStream 可以逐步处理数据,避免一次性加载整个文件,减少内存占用。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 个人博客 所有,欢迎分享本文,转载请保留出处,谢谢!