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()
逐步获取数据块,而不是一次性获取所有数据。