unexpected behavior from `ofetch.raw` and `onResponse`
Describe the change
I'm trying to track the response progress, something which I do all the time when using the native Fetch API. However I'm encountering unexpected behavior with ofetch:
import { ofetch } from "ofetch"
// native fetch: logs progress
fetch("http://localhost:3000")
.then(response =>
onResponse({
response,
onProgress: progress => console.info(progress)
})
)
.catch(console.error)
// Does not log progress
ofetch
.raw("http://localhost:3000")
.then(response =>
onResponse({
response, // also tried passing `response._data` for body
onProgress: progress => console.info(progress)
})
)
.catch(console.error)
// Does not log progress
ofetch("http://localhost:3000")
.then(response =>
onResponse({
response,
onProgress: progress => console.info(progress)
})
)
.catch(console.error)
// Does not log progress
ofetch("http://localhost:3000", {
onResponse: async context =>
onResponse({
response: context.response,
onProgress: async progress => console.info(progress)
})
}).catch(console.error)
type MaybePromise<TValue> = TValue | Promise<TValue>
async function onResponse({
response,
onProgress
}: { response: Response; onProgress: (progress: number) => MaybePromise<void> }) {
console.info(`Response status: ${response.status}`)
const reader = response.body?.getReader()
if (!reader) return
const contentLengthHeader = response.headers.get("content-length")
const length = contentLengthHeader ? +Number.parseInt(contentLengthHeader) : undefined
let receivedLength = 0
let chunks: Array<Uint8Array> = []
while (true) {
console.info("Reading...")
const read = await reader?.read()
if (read?.done || !read?.value) break
receivedLength += read.value.length
chunks.push(read.value)
// Calculate progress as a percentage if length is defined
const progress = length ? (receivedLength / length) * 100 : receivedLength
await onProgress(progress)
console.info(`Received ${receivedLength} of ${length} bytes`)
}
}
testing server:
import http from "node:http"
http
.createServer(async (req, res) => {
res.writeHead(200, { "Content-Type": "text/plain", "Content-Length": "110" })
const chunks = ["Hello ", "World", "\n"]
for (const chunk of chunks) {
res.write(chunk)
await new Promise(resolve => setTimeout(resolve, 1_000)) // Simulate delay between chunks
}
res.end()
})
.listen(3_000, "localhost", () => console.log("Server running at http://localhost:3000/"))
URLs
No response
Additional information
- [X] Would you be willing to help?
This is by design.
ofetch.raw is not the equivalent of native fetch (you can access it using ofetch.native). It only gives you the whole response object after the request has been completed, instead of only the data (.json() / .text() return value). I believe you could set the responseType parameter to 'stream' and it may skip awaiting data, see source. However, if you do so, you'll be responsible for parsing the response (eg. r.text()) yourself.
Also see: https://github.com/unjs/ofetch/issues/45
Let's track progress support via #45