Skip to content

Commit

Permalink
feat: added progress for request and response with json
Browse files Browse the repository at this point in the history
  • Loading branch information
picunada committed Aug 24, 2023
1 parent 314077e commit 0b18ce4
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 1 deletion.
56 changes: 55 additions & 1 deletion src/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,14 @@ export interface FetchOptions<R extends ResponseType = ResponseType>
onRequestError?(
context: FetchContext & { error: Error }
): Promise<void> | void;
onRequestProgress?(progress: number): Promise<void> | void;
onResponse?(
context: FetchContext & { response: FetchResponse<R> }
): Promise<void> | void;
onResponseError?(
context: FetchContext & { response: FetchResponse<R> }
): Promise<void> | void;
onResponseProgress?(progress: number): Promise<void> | void;
}

export interface $Fetch {
Expand Down Expand Up @@ -196,6 +198,28 @@ export function createFetch(globalOptions: CreateFetchOptions = {}): $Fetch {
? context.options.body
: JSON.stringify(context.options.body);

if (context.options.onRequestProgress) {
const { readable, writable } = new TransformStream();
const _writer = writable.getWriter();
const _encoder = new TextEncoder();
const contentLength = _encoder.encode(
context.options.body
).byteLength;
let loaded = 0;

for (const char of context.options.body) {
const chunk = _encoder.encode(char);
loaded += chunk.byteLength;
_writer.write(chunk);
context.options.onRequestProgress(
Math.round((loaded / contentLength) * 100)
);
}

context.options.body = readable;
context.options.duplex = "half";
}

// Set Content-Type and Accept headers to application/json by default
// for JSON serializable request bodies.
// Pass empty object as older browsers don't support undefined.
Expand Down Expand Up @@ -255,7 +279,37 @@ export function createFetch(globalOptions: CreateFetchOptions = {}): $Fetch {
// We override the `.json()` method to parse the body more securely with `destr`
switch (responseType) {
case "json": {
const data = await context.response.text();
const data = await (async function () {
/* Custom response.text() function to retrieve text from response and get progress values */
let loaded = 0;
const contentLength =
context.response!.headers.get("content-length")!;
const _reader = context.response!.body!.getReader();
const _decoder = new TextDecoder();
const _chunks: string[] = [];

async function read(): Promise<string> {
const { done, value } = await _reader.read();

if (done) {
return _chunks.join("");
}

loaded += value.byteLength;

if (context.options.onResponseProgress) {
context.options.onResponseProgress(
Math.round((loaded / Number.parseInt(contentLength)) * 100)
);
}

const chunk = _decoder.decode(value, { stream: true });
_chunks.push(chunk);
return await read(); // read the next chunk
}

return await read();
})();
const parseFunction = context.options.parseResponse || destr;
context.response._data = parseFunction(data);
break;
Expand Down
30 changes: 30 additions & 0 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,36 @@ describe("ofetch", () => {
expect(race).to.equal("timeout");
});

it("return progress in onResponseProgress", async () => {
let loaded = 0;
await $fetch(getURL("post"), {
method: "post",
body: JSON.stringify({
key: "test",
json: true,
}),
onResponseProgress: (progress) => {
loaded = progress;
},
});
expect(loaded).to.equal(100);
});

it("return progress in onRequestProgress", async () => {
let loaded = 0;
await $fetch(getURL("post"), {
method: "post",
body: JSON.stringify({
key: "test",
json: true,
}),
onRequestProgress: (progress) => {
loaded = progress;
},
});
expect(loaded).to.equal(100);
});

it("deep merges defaultOptions", async () => {
const _customFetch = $fetch.create({
query: {
Expand Down

0 comments on commit 0b18ce4

Please sign in to comment.