Fetch API 能实现流式读取数据,关键在于理解其工作机制以及流式读取的时机。
具体流程
-
发起请求:
const response = await fetch('http://localhost:11434/api/generate', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ model: 'llama2', prompt: 'Tell me a story about a dragon.', stream: true, }), });- 此时,浏览器已经发送了请求,并等待服务器的响应。
response是一个Response对象,但响应体数据还没有被读取。
-
获取可读流:
const reader = response.body.getReader();- 这里只是获取了一个可读流的读取器(
ReadableStreamDefaultReader),并没有开始读取数据。
- 这里只是获取了一个可读流的读取器(
-
逐步读取数据:
while (true) { const { done, value } = await reader.read(); if (done) break; // 如果流结束,退出循环 // 处理数据块 const chunk = new TextDecoder().decode(value); const data = JSON.parse(chunk); console.log(data.response); }- 数据是在
reader.read()时逐步读取的。 - 每次调用
reader.read()会从服务器获取一个数据块,直到流结束(done为true)。
- 数据是在
response.body.getReader() 的执行时机
await fetch的机制:- 当你调用
await fetch(...)时,浏览器会发起网络请求,并等待服务器的响应。 - await fetch 的返回值是一个
Response对象,但这个Response对象并不包含完整的响应体数据,而是一个表示 HTTP 响应的抽象对象。 - 此时,响应体(
response.body)是一个 可读流(ReadableStream),数据还没有被完全读取。
- 当你调用
response.body.getReader()的作用:response.body.getReader()是用来从可读流中逐步读取数据的。- 调用
getReader()并不会立即读取数据,而是返回一个ReadableStreamDefaultReader对象,用于后续逐步读取数据块。
- 数据读取的时机:
- 数据是在调用
reader.read()时逐步读取的,而不是在await fetch或getReader()时读取。 - 每次调用
reader.read()会返回一个 Promise,解析为一个包含数据块的对象({ done, value })。
- 数据是在调用
关键点总结
await fetch的作用:- 发起请求并等待响应头返回。
- 返回的
Response对象包含响应头信息和一个可读流(response.body),但响应体数据还没有被读取。
response.body.getReader()的作用:- 获取一个可读流的读取器(
ReadableStreamDefaultReader),用于后续逐步读取数据。 - 调用
getReader()并不会立即读取数据。
- 获取一个可读流的读取器(
- 数据读取的时机:
- 数据是在调用
reader.read()时逐步读取的。 - 每次读取一个数据块,直到流结束。
- 数据是在调用
类比理解
可以将整个过程类比为:
-
打电话(
fetch):- 你拨打电话(发起请求),等待对方接听(服务器响应)。
- 对方接听后,电话接通(
Response对象返回),但还没有开始对话。
-
打开话筒(
getReader()):- 你打开话筒(获取可读流的读取器),准备听对方说话。
- 此时还没有开始接收数据。
-
逐步听对方说话(
reader.read()):- 你开始听对方说话(逐步读取数据块)。
- 每次听到一部分内容(数据块),直到对方说完(流结束)。
总结
response.body.getReader()是在 await fetch 返回Response对象后才执行的,但它并不会立即读取数据。- 数据是在调用
reader.read()时逐步读取的,这才是真正的流式读取。 - 流式读取的核心是通过
reader.read()逐步获取数据块,而不是一次性获取所有数据。
772

被折叠的 条评论
为什么被折叠?



