diff --git a/src/templates/core/ApiError.hbs b/src/templates/core/ApiError.hbs index f8b82941..a9eee2a6 100644 --- a/src/templates/core/ApiError.hbs +++ b/src/templates/core/ApiError.hbs @@ -11,6 +11,7 @@ export class ApiError extends Error { constructor(response: ApiResult, message: string) { super(message); + this.name = 'ApiError'; this.url = response.url; this.status = response.status; this.statusText = response.statusText; diff --git a/src/templates/core/CancelablePromise.hbs b/src/templates/core/CancelablePromise.hbs index 29adb47e..8cceeeb2 100644 --- a/src/templates/core/CancelablePromise.hbs +++ b/src/templates/core/CancelablePromise.hbs @@ -1,10 +1,29 @@ {{>header}} +export class CancelError extends Error { + + constructor(reason: string = 'Promise was canceled') { + super(reason); + this.name = 'CancelError'; + } + + public get isCancelled(): boolean { + return true; + } +} + +export interface OnCancel { + readonly isPending: boolean; + readonly isCancelled: boolean; + + (cancelHandler: () => void): void; +} + export class CancelablePromise implements Promise { readonly [Symbol.toStringTag]: string; #isPending: boolean; - #isCanceled: boolean; + #isCancelled: boolean; readonly #cancelHandlers: (() => void)[]; readonly #promise: Promise; #resolve?: (value: T | PromiseLike) => void; @@ -14,18 +33,18 @@ export class CancelablePromise implements Promise { executor: ( resolve: (value: T | PromiseLike) => void, reject: (reason?: any) => void, - onCancel: (cancelHandler: () => void) => void + onCancel: OnCancel ) => void ) { this.#isPending = true; - this.#isCanceled = false; + this.#isCancelled = false; this.#cancelHandlers = []; this.#promise = new Promise((resolve, reject) => { this.#resolve = resolve; this.#reject = reject; const onResolve = (value: T | PromiseLike): void => { - if (!this.#isCanceled) { + if (!this.#isCancelled) { this.#isPending = false; this.#resolve?.(value); } @@ -42,7 +61,15 @@ export class CancelablePromise implements Promise { } }; - return executor(onResolve, onReject, onCancel); + Object.defineProperty(onCancel, 'isPending', { + get: (): boolean => this.#isPending, + }); + + Object.defineProperty(onCancel, 'isCancelled', { + get: (): boolean => this.#isCancelled, + }); + + return executor(onResolve, onReject, onCancel as OnCancel); }); } @@ -64,10 +91,10 @@ export class CancelablePromise implements Promise { } public cancel(): void { - if (!this.#isPending || this.#isCanceled) { + if (!this.#isPending || this.#isCancelled) { return; } - this.#isCanceled = true; + this.#isCancelled = true; if (this.#cancelHandlers.length) { try { for (const cancelHandler of this.#cancelHandlers) { @@ -80,7 +107,7 @@ export class CancelablePromise implements Promise { } } - public get isCanceled(): boolean { - return this.#isCanceled; + public get isCancelled(): boolean { + return this.#isCancelled; } } diff --git a/src/templates/core/axios/getRequestBody.hbs b/src/templates/core/axios/getRequestBody.hbs new file mode 100644 index 00000000..004108b2 --- /dev/null +++ b/src/templates/core/axios/getRequestBody.hbs @@ -0,0 +1,6 @@ +function getRequestBody(options: ApiRequestOptions): any { + if (options.body) { + return options.body; + } + return; +} diff --git a/src/templates/core/axios/getResponseBody.hbs b/src/templates/core/axios/getResponseBody.hbs index e9572f47..c086c074 100644 --- a/src/templates/core/axios/getResponseBody.hbs +++ b/src/templates/core/axios/getResponseBody.hbs @@ -2,5 +2,5 @@ function getResponseBody(response: AxiosResponse): any { if (response.status !== 204) { return response.data; } - return null; + return; } diff --git a/src/templates/core/axios/getResponseHeader.hbs b/src/templates/core/axios/getResponseHeader.hbs index 2932265e..ed4f24c4 100644 --- a/src/templates/core/axios/getResponseHeader.hbs +++ b/src/templates/core/axios/getResponseHeader.hbs @@ -1,9 +1,9 @@ -function getResponseHeader(response: AxiosResponse, responseHeader?: string): string | null { +function getResponseHeader(response: AxiosResponse, responseHeader?: string): string | undefined { if (responseHeader) { const content = response.headers[responseHeader]; if (isString(content)) { return content; } } - return null; + return; } diff --git a/src/templates/core/axios/request.hbs b/src/templates/core/axios/request.hbs index 8c020f2d..de748572 100644 --- a/src/templates/core/axios/request.hbs +++ b/src/templates/core/axios/request.hbs @@ -7,6 +7,7 @@ import { ApiError } from './ApiError'; import type { ApiRequestOptions } from './ApiRequestOptions'; import type { ApiResult } from './ApiResult'; import { CancelablePromise } from './CancelablePromise'; +import type { OnCancel } from './CancelablePromise'; import { OpenAPI } from './OpenAPI'; {{>functions/isDefined}} @@ -39,6 +40,9 @@ import { OpenAPI } from './OpenAPI'; {{>axios/getHeaders}} +{{>axios/getRequestBody}} + + {{>axios/sendRequest}} @@ -61,21 +65,27 @@ export function request(options: ApiRequestOptions): CancelablePromise { return new CancelablePromise(async (resolve, reject, onCancel) => { try { const url = getUrl(options); - const response = await sendRequest(options, url, onCancel); - const responseBody = getResponseBody(response); - const responseHeader = getResponseHeader(response, options.responseHeader); + const formData = getFormData(options); + const body = getRequestBody(options); + const headers = await getHeaders(options, formData); - const result: ApiResult = { - url, - ok: isSuccess(response.status), - status: response.status, - statusText: response.statusText, - body: responseHeader || responseBody, - }; + if (!onCancel.isCancelled) { + const response = await sendRequest(options, url, formData, body, headers, onCancel); + const responseBody = getResponseBody(response); + const responseHeader = getResponseHeader(response, options.responseHeader); - catchErrors(options, result); + const result: ApiResult = { + url, + ok: isSuccess(response.status), + status: response.status, + statusText: response.statusText, + body: responseHeader || responseBody, + }; - resolve(result.body); + catchErrors(options, result); + + resolve(result.body); + } } catch (error) { reject(error); } diff --git a/src/templates/core/axios/sendRequest.hbs b/src/templates/core/axios/sendRequest.hbs index ffb83708..60378bfa 100644 --- a/src/templates/core/axios/sendRequest.hbs +++ b/src/templates/core/axios/sendRequest.hbs @@ -1,13 +1,18 @@ -async function sendRequest(options: ApiRequestOptions, url: string, onCancel: (cancelHandler: () => void) => void): Promise> { +async function sendRequest( + options: ApiRequestOptions, + url: string, + formData: FormData | undefined, + body: any, + headers: Record, + onCancel: OnCancel +): Promise> { const source = axios.CancelToken.source(); - const formData = options.formData && getFormData(options.formData); - const data = formData || options.body; const config: AxiosRequestConfig = { url, - data, + headers, + data: body || formData, method: options.method, - headers: await getHeaders(options, formData), cancelToken: source.token, }; diff --git a/src/templates/core/fetch/getHeaders.hbs b/src/templates/core/fetch/getHeaders.hbs index 50beaa11..33b7ce4e 100644 --- a/src/templates/core/fetch/getHeaders.hbs +++ b/src/templates/core/fetch/getHeaders.hbs @@ -37,5 +37,6 @@ async function getHeaders(options: ApiRequestOptions): Promise { headers.append('Content-Type', 'application/json'); } } + return headers; } diff --git a/src/templates/core/fetch/getRequestBody.hbs b/src/templates/core/fetch/getRequestBody.hbs index da1d8006..7ce4045c 100644 --- a/src/templates/core/fetch/getRequestBody.hbs +++ b/src/templates/core/fetch/getRequestBody.hbs @@ -1,8 +1,4 @@ function getRequestBody(options: ApiRequestOptions): BodyInit | undefined { - if (options.formData) { - return getFormData(options.formData); - } - if (options.body) { if (options.mediaType?.includes('/json')) { return JSON.stringify(options.body) @@ -12,6 +8,5 @@ function getRequestBody(options: ApiRequestOptions): BodyInit | undefined { return JSON.stringify(options.body); } } - - return undefined; + return; } diff --git a/src/templates/core/fetch/getResponseBody.hbs b/src/templates/core/fetch/getResponseBody.hbs index 37a1f33c..741747d6 100644 --- a/src/templates/core/fetch/getResponseBody.hbs +++ b/src/templates/core/fetch/getResponseBody.hbs @@ -14,6 +14,5 @@ async function getResponseBody(response: Response): Promise { console.error(error); } } - - return null; + return; } diff --git a/src/templates/core/fetch/getResponseHeader.hbs b/src/templates/core/fetch/getResponseHeader.hbs index 1143e3bd..bdc006ac 100644 --- a/src/templates/core/fetch/getResponseHeader.hbs +++ b/src/templates/core/fetch/getResponseHeader.hbs @@ -1,10 +1,9 @@ -function getResponseHeader(response: Response, responseHeader?: string): string | null { +function getResponseHeader(response: Response, responseHeader?: string): string | undefined { if (responseHeader) { const content = response.headers.get(responseHeader); if (isString(content)) { return content; } } - - return null; + return; } diff --git a/src/templates/core/fetch/request.hbs b/src/templates/core/fetch/request.hbs index 3ab2cb36..b37002be 100644 --- a/src/templates/core/fetch/request.hbs +++ b/src/templates/core/fetch/request.hbs @@ -4,6 +4,7 @@ import { ApiError } from './ApiError'; import type { ApiRequestOptions } from './ApiRequestOptions'; import type { ApiResult } from './ApiResult'; import { CancelablePromise } from './CancelablePromise'; +import type { OnCancel } from './CancelablePromise'; import { OpenAPI } from './OpenAPI'; {{>functions/isDefined}} @@ -61,21 +62,27 @@ export function request(options: ApiRequestOptions): CancelablePromise { return new CancelablePromise(async (resolve, reject, onCancel) => { try { const url = getUrl(options); - const response = await sendRequest(options, url, onCancel); - const responseBody = await getResponseBody(response); - const responseHeader = getResponseHeader(response, options.responseHeader); + const formData = getFormData(options); + const body = getRequestBody(options); + const headers = await getHeaders(options); - const result: ApiResult = { - url, - ok: response.ok, - status: response.status, - statusText: response.statusText, - body: responseHeader || responseBody, - }; + if (!onCancel.isCancelled) { + const response = await sendRequest(options, url, formData, body, headers, onCancel); + const responseBody = await getResponseBody(response); + const responseHeader = getResponseHeader(response, options.responseHeader); - catchErrors(options, result); + const result: ApiResult = { + url, + ok: response.ok, + status: response.status, + statusText: response.statusText, + body: responseHeader || responseBody, + }; - resolve(result.body); + catchErrors(options, result); + + resolve(result.body); + } } catch (error) { reject(error); } diff --git a/src/templates/core/fetch/sendRequest.hbs b/src/templates/core/fetch/sendRequest.hbs index 6732df12..dce73248 100644 --- a/src/templates/core/fetch/sendRequest.hbs +++ b/src/templates/core/fetch/sendRequest.hbs @@ -1,10 +1,17 @@ -async function sendRequest(options: ApiRequestOptions, url: string, onCancel: (cancelHandler: () => void) => void): Promise { +async function sendRequest( + options: ApiRequestOptions, + url: string, + formData: FormData | undefined, + body: BodyInit | undefined, + headers: Headers, + onCancel: OnCancel +): Promise { const controller = new AbortController(); - + const request: RequestInit = { + headers, + body: body || formData, method: options.method, - headers: await getHeaders(options), - body: getRequestBody(options), signal: controller.signal, }; diff --git a/src/templates/core/functions/getFormData.hbs b/src/templates/core/functions/getFormData.hbs index a1c33ee5..239fbf37 100644 --- a/src/templates/core/functions/getFormData.hbs +++ b/src/templates/core/functions/getFormData.hbs @@ -1,12 +1,15 @@ -function getFormData(params: Record): FormData { - const formData = new FormData(); +function getFormData(options: ApiRequestOptions): FormData | undefined { + if (options.formData) { + const formData = new FormData(); - Object.keys(params).forEach(key => { - const value = params[key]; - if (isDefined(value)) { - formData.append(key, value); - } - }); + Object.keys(options.formData).forEach(key => { + const value = options.formData?.[key]; + if (isDefined(value)) { + formData.append(key, value); + } + }); - return formData; + return formData; + } + return; } diff --git a/src/templates/core/node/getRequestBody.hbs b/src/templates/core/node/getRequestBody.hbs index 9281c23a..b9be6a09 100644 --- a/src/templates/core/node/getRequestBody.hbs +++ b/src/templates/core/node/getRequestBody.hbs @@ -1,8 +1,4 @@ function getRequestBody(options: ApiRequestOptions): BodyInit | undefined { - if (options.formData) { - return getFormData(options.formData); - } - if (options.body) { if (options.mediaType?.includes('/json')) { return JSON.stringify(options.body) @@ -12,6 +8,5 @@ function getRequestBody(options: ApiRequestOptions): BodyInit | undefined { return JSON.stringify(options.body); } } - - return undefined; + return; } diff --git a/src/templates/core/node/getResponseBody.hbs b/src/templates/core/node/getResponseBody.hbs index 37a1f33c..741747d6 100644 --- a/src/templates/core/node/getResponseBody.hbs +++ b/src/templates/core/node/getResponseBody.hbs @@ -14,6 +14,5 @@ async function getResponseBody(response: Response): Promise { console.error(error); } } - - return null; + return; } diff --git a/src/templates/core/node/getResponseHeader.hbs b/src/templates/core/node/getResponseHeader.hbs index 1143e3bd..bdc006ac 100644 --- a/src/templates/core/node/getResponseHeader.hbs +++ b/src/templates/core/node/getResponseHeader.hbs @@ -1,10 +1,9 @@ -function getResponseHeader(response: Response, responseHeader?: string): string | null { +function getResponseHeader(response: Response, responseHeader?: string): string | undefined { if (responseHeader) { const content = response.headers.get(responseHeader); if (isString(content)) { return content; } } - - return null; + return; } diff --git a/src/templates/core/node/request.hbs b/src/templates/core/node/request.hbs index 3270a53a..3da3012f 100644 --- a/src/templates/core/node/request.hbs +++ b/src/templates/core/node/request.hbs @@ -9,6 +9,7 @@ import { ApiError } from './ApiError'; import type { ApiRequestOptions } from './ApiRequestOptions'; import type { ApiResult } from './ApiResult'; import { CancelablePromise } from './CancelablePromise'; +import type { OnCancel } from './CancelablePromise'; import { OpenAPI } from './OpenAPI'; {{>functions/isDefined}} @@ -69,21 +70,27 @@ export function request(options: ApiRequestOptions): CancelablePromise { return new CancelablePromise(async (resolve, reject, onCancel) => { try { const url = getUrl(options); - const response = await sendRequest(options, url, onCancel); - const responseBody = await getResponseBody(response); - const responseHeader = getResponseHeader(response, options.responseHeader); + const formData = getFormData(options); + const body = getRequestBody(options); + const headers = await getHeaders(options); - const result: ApiResult = { - url, - ok: response.ok, - status: response.status, - statusText: response.statusText, - body: responseHeader || responseBody, - }; + if (!onCancel.isCancelled) { + const response = await sendRequest(options, url, formData, body, headers, onCancel); + const responseBody = await getResponseBody(response); + const responseHeader = getResponseHeader(response, options.responseHeader); - catchErrors(options, result); + const result: ApiResult = { + url, + ok: response.ok, + status: response.status, + statusText: response.statusText, + body: responseHeader || responseBody, + }; - resolve(result.body); + catchErrors(options, result); + + resolve(result.body); + } } catch (error) { reject(error); } diff --git a/src/templates/core/node/sendRequest.hbs b/src/templates/core/node/sendRequest.hbs index 00919c09..236a87c1 100644 --- a/src/templates/core/node/sendRequest.hbs +++ b/src/templates/core/node/sendRequest.hbs @@ -1,10 +1,17 @@ -async function sendRequest(options: ApiRequestOptions, url: string, onCancel: (cancelHandler: () => void) => void): Promise { +async function sendRequest( + options: ApiRequestOptions, + url: string, + formData: FormData | undefined, + body: BodyInit | undefined, + headers: Headers, + onCancel: OnCancel +): Promise { const controller = new AbortController(); const request: RequestInit = { + headers, method: options.method, - headers: await getHeaders(options), - body: getRequestBody(options), + body: body || formData, signal: controller.signal, }; diff --git a/src/templates/core/xhr/getRequestBody.hbs b/src/templates/core/xhr/getRequestBody.hbs index bfef5c2d..bd9d689e 100644 --- a/src/templates/core/xhr/getRequestBody.hbs +++ b/src/templates/core/xhr/getRequestBody.hbs @@ -1,8 +1,4 @@ function getRequestBody(options: ApiRequestOptions): any { - if (options.formData) { - return getFormData(options.formData); - } - if (options.body) { if (options.mediaType?.includes('/json')) { return JSON.stringify(options.body) @@ -13,5 +9,5 @@ function getRequestBody(options: ApiRequestOptions): any { } } - return undefined; + return; } diff --git a/src/templates/core/xhr/getResponseBody.hbs b/src/templates/core/xhr/getResponseBody.hbs index 21fa7f63..00b0f6c4 100644 --- a/src/templates/core/xhr/getResponseBody.hbs +++ b/src/templates/core/xhr/getResponseBody.hbs @@ -14,6 +14,5 @@ function getResponseBody(xhr: XMLHttpRequest): any { console.error(error); } } - - return null; + return; } diff --git a/src/templates/core/xhr/getResponseHeader.hbs b/src/templates/core/xhr/getResponseHeader.hbs index 0b886d14..64e1aab0 100644 --- a/src/templates/core/xhr/getResponseHeader.hbs +++ b/src/templates/core/xhr/getResponseHeader.hbs @@ -1,10 +1,9 @@ -function getResponseHeader(xhr: XMLHttpRequest, responseHeader?: string): string | null { +function getResponseHeader(xhr: XMLHttpRequest, responseHeader?: string): string | undefined { if (responseHeader) { const content = xhr.getResponseHeader(responseHeader); if (isString(content)) { return content; } } - - return null; + return; } diff --git a/src/templates/core/xhr/request.hbs b/src/templates/core/xhr/request.hbs index 93e1506a..274c00fb 100644 --- a/src/templates/core/xhr/request.hbs +++ b/src/templates/core/xhr/request.hbs @@ -4,6 +4,7 @@ import { ApiError } from './ApiError'; import type { ApiRequestOptions } from './ApiRequestOptions'; import type { ApiResult } from './ApiResult'; import { CancelablePromise } from './CancelablePromise'; +import type { OnCancel } from './CancelablePromise'; import { OpenAPI } from './OpenAPI'; {{>functions/isDefined}} @@ -64,21 +65,27 @@ export function request(options: ApiRequestOptions): CancelablePromise { return new CancelablePromise(async (resolve, reject, onCancel) => { try { const url = getUrl(options); - const response = await sendRequest(options, url, onCancel); - const responseBody = getResponseBody(response); - const responseHeader = getResponseHeader(response, options.responseHeader); + const formData = getFormData(options); + const body = getRequestBody(options); + const headers = await getHeaders(options); - const result: ApiResult = { - url, - ok: isSuccess(response.status), - status: response.status, - statusText: response.statusText, - body: responseHeader || responseBody, - }; + if (!onCancel.isCancelled) { + const response = await sendRequest(options, url, formData, body, headers, onCancel); + const responseBody = getResponseBody(response); + const responseHeader = getResponseHeader(response, options.responseHeader); - catchErrors(options, result); + const result: ApiResult = { + url, + ok: isSuccess(response.status), + status: response.status, + statusText: response.statusText, + body: responseHeader || responseBody, + }; - resolve(result.body); + catchErrors(options, result); + + resolve(result.body); + } } catch (error) { reject(error); } diff --git a/src/templates/core/xhr/sendRequest.hbs b/src/templates/core/xhr/sendRequest.hbs index 02d1f893..4addf125 100644 --- a/src/templates/core/xhr/sendRequest.hbs +++ b/src/templates/core/xhr/sendRequest.hbs @@ -1,17 +1,23 @@ -async function sendRequest(options: ApiRequestOptions, url: string, onCancel: (cancelHandler: () => void) => void): Promise { +async function sendRequest( + options: ApiRequestOptions, + url: string, + formData: FormData | undefined, + body: any, + headers: Headers, + onCancel: OnCancel +): Promise { const xhr = new XMLHttpRequest(); xhr.open(options.method, url, true); xhr.withCredentials = OpenAPI.WITH_CREDENTIALS; - const headers = await getHeaders(options); - headers.forEach((value: string, key: string) => { + headers.forEach((value, key) => { xhr.setRequestHeader(key, value); }); return new Promise((resolve, reject) => { xhr.onload = () => resolve(xhr); xhr.onabort = () => reject(new Error('The user aborted a request.')); - xhr.send(getRequestBody(options)); + xhr.send(body || formData); onCancel(() => xhr.abort()); }); diff --git a/src/utils/registerHandlebarTemplates.ts b/src/utils/registerHandlebarTemplates.ts index c443744b..fbea5513 100644 --- a/src/utils/registerHandlebarTemplates.ts +++ b/src/utils/registerHandlebarTemplates.ts @@ -5,6 +5,7 @@ import templateCoreApiError from '../templates/core/ApiError.hbs'; import templateCoreApiRequestOptions from '../templates/core/ApiRequestOptions.hbs'; import templateCoreApiResult from '../templates/core/ApiResult.hbs'; import axiosGetHeaders from '../templates/core/axios/getHeaders.hbs'; +import axiosGetRequestBody from '../templates/core/axios/getRequestBody.hbs'; import axiosGetResponseBody from '../templates/core/axios/getResponseBody.hbs'; import axiosGetResponseHeader from '../templates/core/axios/getResponseHeader.hbs'; import axiosRequest from '../templates/core/axios/request.hbs'; @@ -186,6 +187,7 @@ export function registerHandlebarTemplates(root: { httpClient: HttpClient; useOp // Specific files for the axios client implementation Handlebars.registerPartial('axios/getHeaders', Handlebars.template(axiosGetHeaders)); + Handlebars.registerPartial('axios/getRequestBody', Handlebars.template(axiosGetRequestBody)); Handlebars.registerPartial('axios/getResponseBody', Handlebars.template(axiosGetResponseBody)); Handlebars.registerPartial('axios/getResponseHeader', Handlebars.template(axiosGetResponseHeader)); Handlebars.registerPartial('axios/sendRequest', Handlebars.template(axiosSendRequest)); diff --git a/test/__snapshots__/index.spec.js.snap b/test/__snapshots__/index.spec.js.snap index 6b61d6c5..5f0da11c 100644 --- a/test/__snapshots__/index.spec.js.snap +++ b/test/__snapshots__/index.spec.js.snap @@ -15,6 +15,7 @@ export class ApiError extends Error { constructor(response: ApiResult, message: string) { super(message); + this.name = 'ApiError'; this.url = response.url; this.status = response.status; this.statusText = response.statusText; @@ -58,11 +59,30 @@ exports[`v2 should generate: ./test/generated/v2/core/CancelablePromise.ts 1`] = "/* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ +export class CancelError extends Error { + + constructor(reason: string = 'Promise was canceled') { + super(reason); + this.name = 'CancelError'; + } + + public get isCancelled(): boolean { + return true; + } +} + +export interface OnCancel { + readonly isPending: boolean; + readonly isCancelled: boolean; + + (cancelHandler: () => void): void; +} + export class CancelablePromise implements Promise { readonly [Symbol.toStringTag]: string; #isPending: boolean; - #isCanceled: boolean; + #isCancelled: boolean; readonly #cancelHandlers: (() => void)[]; readonly #promise: Promise; #resolve?: (value: T | PromiseLike) => void; @@ -72,18 +92,18 @@ export class CancelablePromise implements Promise { executor: ( resolve: (value: T | PromiseLike) => void, reject: (reason?: any) => void, - onCancel: (cancelHandler: () => void) => void + onCancel: OnCancel ) => void ) { this.#isPending = true; - this.#isCanceled = false; + this.#isCancelled = false; this.#cancelHandlers = []; this.#promise = new Promise((resolve, reject) => { this.#resolve = resolve; this.#reject = reject; const onResolve = (value: T | PromiseLike): void => { - if (!this.#isCanceled) { + if (!this.#isCancelled) { this.#isPending = false; this.#resolve?.(value); } @@ -100,7 +120,15 @@ export class CancelablePromise implements Promise { } }; - return executor(onResolve, onReject, onCancel); + Object.defineProperty(onCancel, 'isPending', { + get: (): boolean => this.#isPending, + }); + + Object.defineProperty(onCancel, 'isCancelled', { + get: (): boolean => this.#isCancelled, + }); + + return executor(onResolve, onReject, onCancel as OnCancel); }); } @@ -122,10 +150,10 @@ export class CancelablePromise implements Promise { } public cancel(): void { - if (!this.#isPending || this.#isCanceled) { + if (!this.#isPending || this.#isCancelled) { return; } - this.#isCanceled = true; + this.#isCancelled = true; if (this.#cancelHandlers.length) { try { for (const cancelHandler of this.#cancelHandlers) { @@ -138,8 +166,8 @@ export class CancelablePromise implements Promise { } } - public get isCanceled(): boolean { - return this.#isCanceled; + public get isCancelled(): boolean { + return this.#isCancelled; } }" `; @@ -184,6 +212,7 @@ import { ApiError } from './ApiError'; import type { ApiRequestOptions } from './ApiRequestOptions'; import type { ApiResult } from './ApiResult'; import { CancelablePromise } from './CancelablePromise'; +import type { OnCancel } from './CancelablePromise'; import { OpenAPI } from './OpenAPI'; function isDefined(value: T | null | undefined): value is Exclude { @@ -243,17 +272,20 @@ function getUrl(options: ApiRequestOptions): string { return url; } -function getFormData(params: Record): FormData { - const formData = new FormData(); +function getFormData(options: ApiRequestOptions): FormData | undefined { + if (options.formData) { + const formData = new FormData(); - Object.keys(params).forEach(key => { - const value = params[key]; - if (isDefined(value)) { - formData.append(key, value); - } - }); + Object.keys(options.formData).forEach(key => { + const value = options.formData?.[key]; + if (isDefined(value)) { + formData.append(key, value); + } + }); - return formData; + return formData; + } + return; } type Resolver = (options: ApiRequestOptions) => Promise; @@ -304,14 +336,11 @@ async function getHeaders(options: ApiRequestOptions): Promise { headers.append('Content-Type', 'application/json'); } } + return headers; } function getRequestBody(options: ApiRequestOptions): BodyInit | undefined { - if (options.formData) { - return getFormData(options.formData); - } - if (options.body) { if (options.mediaType?.includes('/json')) { return JSON.stringify(options.body) @@ -321,17 +350,23 @@ function getRequestBody(options: ApiRequestOptions): BodyInit | undefined { return JSON.stringify(options.body); } } - - return undefined; + return; } -async function sendRequest(options: ApiRequestOptions, url: string, onCancel: (cancelHandler: () => void) => void): Promise { +async function sendRequest( + options: ApiRequestOptions, + url: string, + formData: FormData | undefined, + body: BodyInit | undefined, + headers: Headers, + onCancel: OnCancel +): Promise { const controller = new AbortController(); - + const request: RequestInit = { + headers, + body: body || formData, method: options.method, - headers: await getHeaders(options), - body: getRequestBody(options), signal: controller.signal, }; @@ -344,15 +379,14 @@ async function sendRequest(options: ApiRequestOptions, url: string, onCancel: (c return await fetch(url, request); } -function getResponseHeader(response: Response, responseHeader?: string): string | null { +function getResponseHeader(response: Response, responseHeader?: string): string | undefined { if (responseHeader) { const content = response.headers.get(responseHeader); if (isString(content)) { return content; } } - - return null; + return; } async function getResponseBody(response: Response): Promise { @@ -371,8 +405,7 @@ async function getResponseBody(response: Response): Promise { console.error(error); } } - - return null; + return; } function catchErrors(options: ApiRequestOptions, result: ApiResult): void { @@ -407,21 +440,27 @@ export function request(options: ApiRequestOptions): CancelablePromise { return new CancelablePromise(async (resolve, reject, onCancel) => { try { const url = getUrl(options); - const response = await sendRequest(options, url, onCancel); - const responseBody = await getResponseBody(response); - const responseHeader = getResponseHeader(response, options.responseHeader); + const formData = getFormData(options); + const body = getRequestBody(options); + const headers = await getHeaders(options); - const result: ApiResult = { - url, - ok: response.ok, - status: response.status, - statusText: response.statusText, - body: responseHeader || responseBody, - }; + if (!onCancel.isCancelled) { + const response = await sendRequest(options, url, formData, body, headers, onCancel); + const responseBody = await getResponseBody(response); + const responseHeader = getResponseHeader(response, options.responseHeader); - catchErrors(options, result); + const result: ApiResult = { + url, + ok: response.ok, + status: response.status, + statusText: response.statusText, + body: responseHeader || responseBody, + }; - resolve(result.body); + catchErrors(options, result); + + resolve(result.body); + } } catch (error) { reject(error); } @@ -2488,6 +2527,7 @@ export class ApiError extends Error { constructor(response: ApiResult, message: string) { super(message); + this.name = 'ApiError'; this.url = response.url; this.status = response.status; this.statusText = response.statusText; @@ -2531,11 +2571,30 @@ exports[`v3 should generate: ./test/generated/v3/core/CancelablePromise.ts 1`] = "/* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ +export class CancelError extends Error { + + constructor(reason: string = 'Promise was canceled') { + super(reason); + this.name = 'CancelError'; + } + + public get isCancelled(): boolean { + return true; + } +} + +export interface OnCancel { + readonly isPending: boolean; + readonly isCancelled: boolean; + + (cancelHandler: () => void): void; +} + export class CancelablePromise implements Promise { readonly [Symbol.toStringTag]: string; #isPending: boolean; - #isCanceled: boolean; + #isCancelled: boolean; readonly #cancelHandlers: (() => void)[]; readonly #promise: Promise; #resolve?: (value: T | PromiseLike) => void; @@ -2545,18 +2604,18 @@ export class CancelablePromise implements Promise { executor: ( resolve: (value: T | PromiseLike) => void, reject: (reason?: any) => void, - onCancel: (cancelHandler: () => void) => void + onCancel: OnCancel ) => void ) { this.#isPending = true; - this.#isCanceled = false; + this.#isCancelled = false; this.#cancelHandlers = []; this.#promise = new Promise((resolve, reject) => { this.#resolve = resolve; this.#reject = reject; const onResolve = (value: T | PromiseLike): void => { - if (!this.#isCanceled) { + if (!this.#isCancelled) { this.#isPending = false; this.#resolve?.(value); } @@ -2573,7 +2632,15 @@ export class CancelablePromise implements Promise { } }; - return executor(onResolve, onReject, onCancel); + Object.defineProperty(onCancel, 'isPending', { + get: (): boolean => this.#isPending, + }); + + Object.defineProperty(onCancel, 'isCancelled', { + get: (): boolean => this.#isCancelled, + }); + + return executor(onResolve, onReject, onCancel as OnCancel); }); } @@ -2595,10 +2662,10 @@ export class CancelablePromise implements Promise { } public cancel(): void { - if (!this.#isPending || this.#isCanceled) { + if (!this.#isPending || this.#isCancelled) { return; } - this.#isCanceled = true; + this.#isCancelled = true; if (this.#cancelHandlers.length) { try { for (const cancelHandler of this.#cancelHandlers) { @@ -2611,8 +2678,8 @@ export class CancelablePromise implements Promise { } } - public get isCanceled(): boolean { - return this.#isCanceled; + public get isCancelled(): boolean { + return this.#isCancelled; } }" `; @@ -2657,6 +2724,7 @@ import { ApiError } from './ApiError'; import type { ApiRequestOptions } from './ApiRequestOptions'; import type { ApiResult } from './ApiResult'; import { CancelablePromise } from './CancelablePromise'; +import type { OnCancel } from './CancelablePromise'; import { OpenAPI } from './OpenAPI'; function isDefined(value: T | null | undefined): value is Exclude { @@ -2716,17 +2784,20 @@ function getUrl(options: ApiRequestOptions): string { return url; } -function getFormData(params: Record): FormData { - const formData = new FormData(); +function getFormData(options: ApiRequestOptions): FormData | undefined { + if (options.formData) { + const formData = new FormData(); - Object.keys(params).forEach(key => { - const value = params[key]; - if (isDefined(value)) { - formData.append(key, value); - } - }); + Object.keys(options.formData).forEach(key => { + const value = options.formData?.[key]; + if (isDefined(value)) { + formData.append(key, value); + } + }); - return formData; + return formData; + } + return; } type Resolver = (options: ApiRequestOptions) => Promise; @@ -2777,14 +2848,11 @@ async function getHeaders(options: ApiRequestOptions): Promise { headers.append('Content-Type', 'application/json'); } } + return headers; } function getRequestBody(options: ApiRequestOptions): BodyInit | undefined { - if (options.formData) { - return getFormData(options.formData); - } - if (options.body) { if (options.mediaType?.includes('/json')) { return JSON.stringify(options.body) @@ -2794,17 +2862,23 @@ function getRequestBody(options: ApiRequestOptions): BodyInit | undefined { return JSON.stringify(options.body); } } - - return undefined; + return; } -async function sendRequest(options: ApiRequestOptions, url: string, onCancel: (cancelHandler: () => void) => void): Promise { +async function sendRequest( + options: ApiRequestOptions, + url: string, + formData: FormData | undefined, + body: BodyInit | undefined, + headers: Headers, + onCancel: OnCancel +): Promise { const controller = new AbortController(); - + const request: RequestInit = { + headers, + body: body || formData, method: options.method, - headers: await getHeaders(options), - body: getRequestBody(options), signal: controller.signal, }; @@ -2817,15 +2891,14 @@ async function sendRequest(options: ApiRequestOptions, url: string, onCancel: (c return await fetch(url, request); } -function getResponseHeader(response: Response, responseHeader?: string): string | null { +function getResponseHeader(response: Response, responseHeader?: string): string | undefined { if (responseHeader) { const content = response.headers.get(responseHeader); if (isString(content)) { return content; } } - - return null; + return; } async function getResponseBody(response: Response): Promise { @@ -2844,8 +2917,7 @@ async function getResponseBody(response: Response): Promise { console.error(error); } } - - return null; + return; } function catchErrors(options: ApiRequestOptions, result: ApiResult): void { @@ -2880,21 +2952,27 @@ export function request(options: ApiRequestOptions): CancelablePromise { return new CancelablePromise(async (resolve, reject, onCancel) => { try { const url = getUrl(options); - const response = await sendRequest(options, url, onCancel); - const responseBody = await getResponseBody(response); - const responseHeader = getResponseHeader(response, options.responseHeader); + const formData = getFormData(options); + const body = getRequestBody(options); + const headers = await getHeaders(options); - const result: ApiResult = { - url, - ok: response.ok, - status: response.status, - statusText: response.statusText, - body: responseHeader || responseBody, - }; + if (!onCancel.isCancelled) { + const response = await sendRequest(options, url, formData, body, headers, onCancel); + const responseBody = await getResponseBody(response); + const responseHeader = getResponseHeader(response, options.responseHeader); - catchErrors(options, result); + const result: ApiResult = { + url, + ok: response.ok, + status: response.status, + statusText: response.statusText, + body: responseHeader || responseBody, + }; - resolve(result.body); + catchErrors(options, result); + + resolve(result.body); + } } catch (error) { reject(error); } diff --git a/test/e2e/v2.babel.spec.js b/test/e2e/v2.babel.spec.js index 4bb7bd01..64568cfa 100644 --- a/test/e2e/v2.babel.spec.js +++ b/test/e2e/v2.babel.spec.js @@ -6,7 +6,7 @@ const compileWithBabel = require('./scripts/compileWithBabel'); const server = require('./scripts/server'); const browser = require('./scripts/browser'); -describe('v2.fetch', () => { +describe('v2.babel', () => { beforeAll(async () => { await generate('v2/babel', 'v2', 'fetch', true, true); await copy('v2/babel'); diff --git a/test/e2e/v2.node.spec.js b/test/e2e/v2.node.spec.js index 965a5b30..1609d656 100644 --- a/test/e2e/v2.node.spec.js +++ b/test/e2e/v2.node.spec.js @@ -38,7 +38,7 @@ describe('v2.node', () => { it('can abort the request', async () => { try { - const { SimpleService } = require('./generated/v3/node/index.js'); + const { SimpleService } = require('./generated/v2/node/index.js'); const promise = SimpleService.getCallWithoutParametersAndResponse(); setTimeout(() => { promise.cancel(); diff --git a/test/e2e/v3.babel.spec.js b/test/e2e/v3.babel.spec.js index 8635c442..26e7268e 100644 --- a/test/e2e/v3.babel.spec.js +++ b/test/e2e/v3.babel.spec.js @@ -6,7 +6,7 @@ const compileWithBabel = require('./scripts/compileWithBabel'); const server = require('./scripts/server'); const browser = require('./scripts/browser'); -describe('v3.fetch', () => { +describe('v3.babel', () => { beforeAll(async () => { await generate('v3/babel', 'v3', 'fetch', true, true); await copy('v3/babel');