From 0b18ce4b3dbb2c8735bdafbd526dcce02691dd7a Mon Sep 17 00:00:00 2001 From: Daniil Bezuglov <xlaystgoku@gmail.com> Date: Thu, 24 Aug 2023 12:33:42 +0700 Subject: [PATCH 1/2] feat: added progress for request and response with json --- src/fetch.ts | 56 +++++++++++++++++++++++++++++++++++++++++++++- test/index.test.ts | 30 +++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/src/fetch.ts b/src/fetch.ts index 740a9f30..70bacfe9 100644 --- a/src/fetch.ts +++ b/src/fetch.ts @@ -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 { @@ -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. @@ -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; diff --git a/test/index.test.ts b/test/index.test.ts index de25338f..9403907d 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -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: { From 12c07f1cf9f6241c1af555aad27a006382dc04da Mon Sep 17 00:00:00 2001 From: Pooya Parsa <pooya@pi0.io> Date: Thu, 26 Oct 2023 20:07:10 +0200 Subject: [PATCH 2/2] fix types --- src/fetch.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/fetch.ts b/src/fetch.ts index 2237c3a8..881c77bd 100644 --- a/src/fetch.ts +++ b/src/fetch.ts @@ -209,12 +209,15 @@ export function createFetch(globalOptions: CreateFetchOptions = {}): $Fetch { /* 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(); + context.response?.headers.get("content-length") || "0"; + const _reader = context.response?.body?.getReader(); const _decoder = new TextDecoder(); const _chunks: string[] = []; async function read(): Promise<string> { + if (!_reader) { + return ""; + } const { done, value } = await _reader.read(); if (done) {