Skip to content

Commit

Permalink
Merge pull request #3407 from easyops-cn/steve/v3-refine-http
Browse files Browse the repository at this point in the history
Steve/v3-refine-http
  • Loading branch information
WHChen-Alex authored Aug 28, 2023
2 parents ab2c712 + 51b68bf commit f61d61a
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 68 deletions.
20 changes: 12 additions & 8 deletions packages/brick-container/src/bootstrap.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// istanbul ignore file
import { createRuntime, httpErrorToString } from "@next-core/runtime";
import { http, HttpError, HttpResponse } from "@next-core/http";
import { HttpRequestConfig, http } from "@next-core/http";
import { i18n } from "@next-core/i18n";
import {
flowApi,
Expand All @@ -19,9 +19,7 @@ import { getSpanId } from "./utils.js";
import { listen } from "./preview/listen.js";

http.interceptors.request.use((config) => {
if (!config.options?.interceptorParams?.ignoreLoadingBar) {
window.dispatchEvent(new Event("request.start"));
}
dispatchRequestEventByConfig("request.start", config);

const headers = new Headers(config.options?.headers || {});

Expand Down Expand Up @@ -50,16 +48,22 @@ http.interceptors.request.use((config) => {
});

http.interceptors.response.use(
function (response: HttpResponse) {
window.dispatchEvent(new Event("request.end"));
function (response, config) {
dispatchRequestEventByConfig("request.end", config);
return response;
},
function (error: HttpError) {
window.dispatchEvent(new Event("request.end"));
function (error, config) {
dispatchRequestEventByConfig("request.end", config);
return Promise.reject(error);
}
);

function dispatchRequestEventByConfig(type: string, config: HttpRequestConfig) {
if (!config.options?.interceptorParams?.ignoreLoadingBar) {
window.dispatchEvent(new Event(type));
}
}

const loadingBar = document.querySelector("#global-loading-bar")!;
loadingBar.classList.add("rendered");

Expand Down
16 changes: 8 additions & 8 deletions packages/http/src/InterceptorManager.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import type { HttpError } from "./http.js";

export interface InterceptorHandlers<T> {
fulfilled?: (config: T) => void;
rejected?: (error: HttpError) => void;
export interface InterceptorHandlers<T, C = never> {
fulfilled?: (value: T, config: C) => T | Promise<T>;
rejected?: (error: HttpError, config: C) => HttpError | Promise<HttpError>;
}

export default class InterceptorManager<T> {
handlers: (InterceptorHandlers<T> | null)[] = [];
export default class InterceptorManager<T, C = never> {
handlers: (InterceptorHandlers<T, C> | null)[] = [];

use(
onFulfilled?: (value: T) => T | Promise<T>,
onRejected?: (error: HttpError) => HttpError | Promise<HttpError>
onFulfilled?: (value: T, config: C) => T | Promise<T>,
onRejected?: (error: HttpError, config: C) => HttpError | Promise<HttpError>
): number {
this.handlers.push({
fulfilled: onFulfilled,
Expand All @@ -27,7 +27,7 @@ export default class InterceptorManager<T> {
}
}

forEach(fn: (h: InterceptorHandlers<T>) => void): void {
forEach(fn: (h: InterceptorHandlers<T, C>) => void): void {
this.handlers.forEach((handler) => {
// istanbul ignore else
if (handler !== null) {
Expand Down
7 changes: 0 additions & 7 deletions packages/http/src/__snapshots__/http.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -398,13 +398,6 @@ exports[`http requestWithBody or simpleRequest PUT http://example.com with [] sh
exports[`http should return http response object 1`] = `
{
"config": {
"method": "GET",
"options": {
"observe": "response",
},
"url": "http://example.com",
},
"data": {
"foo": "bar",
},
Expand Down
50 changes: 47 additions & 3 deletions packages/http/src/http.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,24 @@ type TestItem =
return "<URLSearchParams> " + this.toString();
};

const requestInterceptor = jest.fn((conf) => conf);
const responseInterceptor = jest.fn((response) => response);
const responseRejectInterceptor = jest.fn((error) => Promise.reject(error));

describe("http", () => {
afterEach(() => {
spyOnFetch.mockClear();
let requestInterceptorId: number;
let responseInterceptorId: number;
beforeAll(() => {
requestInterceptorId = http.interceptors.request.use(requestInterceptor);
responseInterceptorId = http.interceptors.response.use(
responseInterceptor,
responseRejectInterceptor
);
});

afterAll(() => {
http.interceptors.request.eject(requestInterceptorId);
http.interceptors.response.eject(responseInterceptorId);
});

const formData = new FormData();
Expand Down Expand Up @@ -127,6 +142,27 @@ describe("http", () => {
});

expect(spyOnFetch.mock.calls[0]).toMatchSnapshot();
expect(requestInterceptor).toBeCalledTimes(1);
expect(responseInterceptor).toBeCalledTimes(1);
expect(responseRejectInterceptor).not.toBeCalled();
expect(requestInterceptor).toBeCalledWith({
url: "http://example.com/for-good",
method: "GET",
options: {},
});
expect(responseInterceptor).toBeCalledWith(
{
status: 200,
statusText: "",
data: {},
headers: expect.any(Headers),
},
{
url: "http://example.com/for-good",
method: "GET",
options: {},
}
);
});

it("should work with getUrlWithParams", () => {
Expand Down Expand Up @@ -209,11 +245,19 @@ describe("http", () => {

it("should throw a HttpParseError", async () => {
__setReturnValue(Promise.resolve(new Response("non-json")));
expect.assertions(1);
expect.assertions(4);
try {
await http.get("http://example.com");
} catch (e) {
expect(e).toBeInstanceOf(HttpParseError);
expect(responseInterceptor).not.toBeCalled();
expect(responseRejectInterceptor).toBeCalledTimes(1);
expect(responseRejectInterceptor).toBeCalledWith(
e,
expect.objectContaining({
url: "http://example.com",
})
);
}
});

Expand Down
62 changes: 20 additions & 42 deletions packages/http/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export interface HttpResponse<T = unknown> {
status: number;
statusText: string;
headers: Headers;
config: HttpRequestConfig;
}

export interface HttpError {
Expand All @@ -36,8 +35,6 @@ export interface HttpConstructorOptions {
adapter?: HttpAdapter;
}

// type NotNil<T> = T extends null ? never : T;

function isNil(value: unknown): value is null | undefined {
return value === undefined || value === null;
}
Expand Down Expand Up @@ -67,16 +64,6 @@ export type HttpOptions = HttpCustomOptions & RequestInit;
export const isHttpAbortError = (error: any) =>
error instanceof DOMException && error.code === 20;

const createError = (
error: HttpFetchError | HttpResponseError | HttpParseError | HttpAbortError,
config: HttpRequestConfig
): HttpError => {
return {
error,
config,
};
};

const request = async <T>(
url: string,
init: RequestInit,
Expand All @@ -98,12 +85,9 @@ const request = async <T>(
response = await fetch(url, init);
} catch (e: any) {
reject(
createError(
isHttpAbortError(e)
? new HttpAbortError(e.toString())
: new HttpFetchError(e.toString()),
config
)
isHttpAbortError(e)
? new HttpAbortError(e.toString())
: new HttpFetchError(e.toString())
);
return;
}
Expand All @@ -115,9 +99,7 @@ const request = async <T>(
} catch (e) {
// Do nothing.
}
reject(
createError(new HttpResponseError(response, responseJson), config)
);
reject(new HttpResponseError(response, responseJson));
return;
}

Expand All @@ -126,18 +108,14 @@ const request = async <T>(
data = await response[responseType]();
} catch (e: any) {
reject(
createError(
isHttpAbortError(e)
? new HttpAbortError(e.toString())
: new HttpParseError(response),
config
)
isHttpAbortError(e)
? new HttpAbortError(e.toString())
: new HttpParseError(response)
);
return;
}

const res: HttpResponse<T> = {
config,
status: response.status,
statusText: response.statusText,
headers: response.headers,
Expand Down Expand Up @@ -214,11 +192,13 @@ const simpleRequest = <T = unknown>(
): Promise<HttpResponse<T>> => {
const {
params,
/* eslint-disable @typescript-eslint/no-unused-vars */
responseType,
interceptorParams,
observe,
noAbortOnRouteChange,
useCache,
/* eslint-enable @typescript-eslint/no-unused-vars */
...requestInit
} = config.options || {};
return request<T>(
Expand All @@ -239,12 +219,14 @@ const requestWithBody = <T = unknown>(
): Promise<HttpResponse<T>> => {
const {
params,
headers,
/* eslint-disable @typescript-eslint/no-unused-vars */
responseType,
interceptorParams,
observe,
noAbortOnRouteChange,
useCache,
headers,
/* eslint-enable @typescript-eslint/no-unused-vars */
...requestInit
} = config.options || {};
return request<T>(
Expand Down Expand Up @@ -273,7 +255,7 @@ const defaultAdapter: HttpAdapter = <T>(config: HttpRequestConfig) => {
class Http {
public readonly interceptors: {
request: InterceptorManager<HttpRequestConfig>;
response: InterceptorManager<HttpResponse>;
response: InterceptorManager<HttpResponse, HttpRequestConfig>;
};

#adapter: HttpAdapter = defaultAdapter;
Expand Down Expand Up @@ -331,19 +313,15 @@ class Http {
chain.push((config: HttpRequestConfig) => this.#adapter(config), undefined);

this.interceptors.response.forEach((interceptor) => {
chain.push(interceptor.fulfilled, interceptor.rejected);
chain.push(
(res: HttpResponse) => interceptor.fulfilled?.(res, config),
(error: HttpError) => interceptor.rejected?.(error, config)
);
});

chain.push(
(response: HttpResponse) => {
return response.config.options?.observe === "response"
? response
: response.data;
},
(error: HttpError) => {
return Promise.reject(error.error);
}
);
chain.push((response: HttpResponse) => {
return config.options?.observe === "response" ? response : response.data;
}, undefined);

while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
Expand Down

0 comments on commit f61d61a

Please sign in to comment.