Skip to content

Commit 5aba27e

Browse files
committed
feat: 🎸 wip
1 parent 8a59c62 commit 5aba27e

File tree

14 files changed

+618
-478
lines changed

14 files changed

+618
-478
lines changed
Lines changed: 34 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,24 @@
1-
import type { generateRequestInfo } from './request-transform';
2-
import type { CommonResponse } from './interceptor';
3-
import type { ClientOptions, Params, FetchPolicy, RequestConfig } from './types';
1+
import type { FetchPolicy } from './types';
42
import type { HydrationStatus } from '../store';
53
import { ReplaySubject } from 'rxjs';
64
import { MODE } from '../const';
75

86
import { createCache, hash } from '../cache';
97

10-
import { deepMerge } from '../utils';
118
import { createInterceptor } from './interceptor';
9+
import { type CommonExtraParams } from '..';
1210

13-
14-
const DEFAULT_OPTIONS: ClientOptions = {
15-
method: 'GET',
16-
headers: {
17-
'content-type': 'application/json',
18-
},
19-
timeout: 60 * 1000,
20-
credentials: 'include',
21-
};
22-
23-
// TODO后续再优化下逻辑写法,比如对于method的定义,需要定义好client与options的边界,拆分通用merge和转换成requestInit的部分……
24-
function mergeClientOptionsAndParams(options: ClientOptions, params: Params): RequestConfig {
25-
const {
26-
timeout,
27-
headers,
28-
method,
29-
credentials,
30-
baseUrl,
31-
} = options;
32-
const url = baseUrl
33-
? new URL(params.url || '/', baseUrl).href
34-
: params.url || '';
35-
36-
const commonConfig = {
37-
timeout: params.timeout || timeout,
38-
headers: deepMerge({}, headers, params.headers),
39-
credentials,
40-
url: url || '',
41-
variables: params.variables,
42-
};
43-
44-
if ('method' in params) {
45-
return {
46-
...commonConfig,
47-
method: params.method || method,
48-
fetchPolicy: params.fetchPolicy,
49-
};
50-
}
51-
52-
if ('query' in params) {
53-
return {
54-
...commonConfig,
55-
query: params.query,
56-
fetchPolicy: params.fetchPolicy,
57-
};
58-
}
59-
60-
if ('mutation' in params) {
61-
return {
62-
...commonConfig,
63-
mutation: params.mutation,
64-
}
65-
}
66-
67-
return commonConfig;
68-
}
69-
70-
class TimeoutError extends Error {
71-
constructor(msg: string) {
72-
super(msg)
73-
this.message = msg;
74-
}
11+
export type CustomClient<Params, Response, Variables extends Record<string, any>, ExtraParams extends CommonExtraParams> = {
12+
transformRequestParams(variables: Variables, extraParams: ExtraParams, mode: typeof MODE): Params
13+
hashVariables?(variables: Variables, hash: (v: any) => string): string
14+
request(params: Params, mode: typeof MODE): Promise<Response>
7515
}
7616

77-
export function clientFactory(
78-
type: 'GQL' | 'REST',
79-
createRequestInfo: typeof generateRequestInfo,
80-
options?: ClientOptions,
81-
) {
82-
const opts = options
83-
? deepMerge({} as ClientOptions, DEFAULT_OPTIONS, options)
84-
: deepMerge({} as ClientOptions, DEFAULT_OPTIONS);
17+
export function createCustomClient<Params, Response, Variables extends Record<string, any>, ExtraParams extends CommonExtraParams>(type: string, client: CustomClient<Params, Response, Variables, ExtraParams>, options: {
18+
cache?: ReturnType<typeof createCache>
19+
}) {
8520
const requestInterceptor = createInterceptor<Params>('request');
86-
const responseInterceptor = createInterceptor<CommonResponse>('response');
21+
const responseInterceptor = createInterceptor<Response>('response');
8722

8823
const interceptors = {
8924
request: {
@@ -94,164 +29,70 @@ export function clientFactory(
9429
},
9530
};
9631

97-
function request<T>(params: Params): Promise<T> {
32+
function request<T>(variables: Variables, extraParams: ExtraParams): Promise<T> {
9833
// 处理前置拦截器
9934

10035
const list = [...requestInterceptor.list];
10136

102-
const config = mergeClientOptionsAndParams(opts, params);
37+
const params = client.transformRequestParams(variables, extraParams, MODE)
10338

10439
// 后面再做benchmark看看一个tick会差出来多少性能
105-
let promise = Promise.resolve(config);
40+
let promise = Promise.resolve(params);
10641

10742
while (list.length) {
10843
const item = list.shift();
10944
promise = promise.then(item?.onResolve, item?.onReject);
11045
}
11146

112-
let request: ReturnType<typeof createRequestInfo>;
113-
11447
return promise.then(params => {
115-
// TODO这里的
116-
request = createRequestInfo(type, params);
117-
118-
if (!opts.fetch) {
119-
// 避免Node环境下的判断,所以没法简化写=。=,因为window.fetch会触发一次RHS导致报错
120-
if (MODE === 'SPA') {
121-
// 小程序因为没有window,所以需要这里绕一下
122-
if (typeof window !== 'undefined' && window.fetch) {
123-
opts.fetch = (resource, options) => window.fetch(resource, options);
124-
} else {
125-
throw new Error('There is no useful "fetch" function');
126-
}
127-
} else if (MODE === 'SSR') {
128-
if (globalThis.fetch) {
129-
opts.fetch = (resource, options) => globalThis.fetch(resource, options);
130-
} else {
131-
throw new Error('There is no useful "fetch" function');
132-
}
133-
}
134-
}
135-
136-
const {
137-
url,
138-
requestInit,
139-
} = request;
140-
const fetchPromise = opts.fetch!(url, requestInit);
141-
142-
const timeoutPromise = new Promise<DOMException | TimeoutError>((resolve) => {
143-
setTimeout(
144-
() => {
145-
if (MODE ==='SSR') {
146-
resolve(new TimeoutError('The request has been timeout'))
147-
} else {
148-
resolve(new DOMException('The request has been timeout'))
149-
}
150-
},
151-
config.timeout,
152-
);
153-
});
154-
return Promise.race([timeoutPromise, fetchPromise]);
155-
}).then((res) => {
156-
// 浏览器断网情况下有可能会是null
157-
if (res === null) {
158-
res = MODE === 'SSR'
159-
? new TimeoutError('The request has been timeout')
160-
: new DOMException('The request has been timeout');
161-
}
162-
48+
let promise = client.request!(params, MODE);
16349
const list = [...responseInterceptor.list];
164-
165-
// 用duck type绕过类型判断
166-
if (!('status' in res)) {
167-
let promise: Promise<any> = Promise.reject({
168-
res,
169-
request,
170-
});
171-
while (list.length) {
172-
const transform = list.shift();
173-
promise = promise.then(transform?.onResolve, transform?.onReject);
174-
}
175-
return promise;
176-
}
177-
178-
const receiveType = res.headers.get('Content-Type')
179-
|| (request.requestInit.headers as Record<string, string>)?.['Content-Type']
180-
|| (request.requestInit.headers as Record<string, string>)?.['content-type']
181-
|| 'application/json';
182-
183-
const commonInfo: CommonResponse = {
184-
status: res.status,
185-
statusText: res.statusText,
186-
headers: res.headers,
187-
config: request,
188-
data: undefined,
189-
};
190-
191-
let promise;
192-
if (receiveType.indexOf('application/json') !== -1) {
193-
promise = res.ok
194-
? res.json().then(data => {
195-
commonInfo.data = data;
196-
return commonInfo;
197-
})
198-
: Promise.reject({
199-
res,
200-
request,
201-
})
202-
} else {
203-
commonInfo.data = res.body;
204-
// 其它类型就把body先扔回去……也许以后有用……
205-
promise = res.ok ? Promise.resolve(commonInfo) : Promise.reject({
206-
res,
207-
request,
208-
});
209-
}
21050
while (list.length) {
21151
const transform = list.shift();
21252
promise = promise.then(transform?.onResolve, transform?.onReject);
21353
}
214-
215-
return promise;
216-
});
54+
return promise as any;
55+
})
21756
}
21857

21958
const cache = options?.cache || createCache();
22059

221-
function getDataFromCache<T>(params: Parameters<typeof request>[0]) {
222-
const data = cache.get<T>(`${hash(params.url)}-${hash(params.variables || {})}`);
60+
function getDataFromCache<T>(variables: Parameters<typeof request>[0]) {
61+
const key = (client.hashVariables ?? hash)(variables, hash)
62+
const data = cache.get<T>(key);
22363
return data;
22464
}
22565

226-
function setDataToCache<T>(params: Parameters<typeof request>[0], data: T) {
227-
const key = `${hash(params.url)}-${hash(params.variables || {})}`;
66+
function setDataToCache<T>(variables: Parameters<typeof request>[0], data: T) {
67+
const key = (client.hashVariables ?? hash)(variables, hash)
22868
cache.put(key, data);
22969
}
23070

23171
function requestWithCache<T>(
232-
params: Parameters<typeof request>[0],
72+
variables: Parameters<typeof request>[0],
73+
extraParams: Parameters<typeof request>[1],
23374
fetchPolicy: FetchPolicy = 'network-first',
23475
hydrationStatus: HydrationStatus,
23576
): ReplaySubject<T> {
23677
const subject = new ReplaySubject<T>();
23778
// 处于Hydration阶段,一律先从缓存里面拿
23879
if (hydrationStatus.value !== 2) {
239-
const data = getDataFromCache<T>(params);
80+
const data = getDataFromCache<T>(variables);
24081
if (data) {
24182
subject.next(data);
24283
subject.complete();
24384
return subject;
24485
}
24586
}
246-
const data = getDataFromCache<T>(params);
87+
const data = getDataFromCache<T>(variables);
24788
switch (fetchPolicy) {
24889
case 'cache-and-network':
24990
if (data) {
25091
subject.next(data);
25192
}
252-
request<T>(params).then(data => {
93+
request<T>(variables, extraParams).then(data => {
25394
// TODO还差分发network status出去
254-
setDataToCache(params, data);
95+
setDataToCache(variables, data);
25596
subject.next(data);
25697
subject.complete();
25798
}).catch(e => {
@@ -263,17 +104,17 @@ export function clientFactory(
263104
if (data) {
264105
subject.next(data);
265106
} else {
266-
request<T>(params).then(data => {
107+
request<T>(variables, extraParams).then(data => {
267108
// TODO还差分发network status出去
268-
setDataToCache(params, data);
109+
setDataToCache(variables, data);
269110
subject.next(data);
270111
subject.complete();
271112
}).catch(e => subject.error(e));
272113
}
273114
break;
274115
case 'network-first':
275-
request<T>(params).then(data => {
276-
setDataToCache(params, data);
116+
request<T>(variables, extraParams).then(data => {
117+
setDataToCache(variables, data);
277118
subject.next(data);
278119
subject.complete();
279120
}).catch(e => {
@@ -291,7 +132,7 @@ export function clientFactory(
291132
}
292133
break;
293134
case 'network-only':
294-
request<T>(params)
135+
request<T>(variables, extraParams)
295136
.then(data => {
296137
subject.next(data);
297138
subject.complete();
@@ -306,11 +147,11 @@ export function clientFactory(
306147

307148
return subject;
308149
}
309-
310150
return {
311151
interceptors,
312152
query: requestWithCache,
313153
mutate: request,
314154
type
315-
};
155+
}
156+
316157
}
Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import type { ClientOptions } from './types';
2-
3-
import { clientFactory } from './client-factory';
4-
import { generateRequestInfo } from './request-transform';
2+
import { createRestClient } from './rest-client';
53

64
export type * from './types';
75

86
export function createClient(type: 'GQL' | 'REST', options?: ClientOptions) {
9-
return clientFactory(type, generateRequestInfo, options)
7+
return createRestClient(options)
108
}

0 commit comments

Comments
 (0)