• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

alovajs / alova / #210

08 Oct 2024 07:26AM CUT coverage: 93.734% (-0.09%) from 93.826%
#210

push

github

web-flow
Merge pull request #554 from alovajs/changeset-release/main

ci: release

1608 of 1761 branches covered (91.31%)

Branch coverage included in aggregate %.

9537 of 10129 relevant lines covered (94.16%)

60.43 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.6
/packages/alova/src/functions/sendRequest.ts
1
import defaultCacheLogger from '@/defaults/cacheLogger';
1✔
2
import { getRawWithCacheAdapter, getWithCacheAdapter, setWithCacheAdapter } from '@/storage/cacheWrapper';
1✔
3
import cloneMethod from '@/utils/cloneMethod';
1✔
4
import {
1✔
5
  $self,
1✔
6
  getConfig,
1✔
7
  getContext,
1✔
8
  getLocalCacheConfigParam,
1✔
9
  getMethodInternalKey,
1✔
10
  getOptions,
1✔
11
  isFn,
1✔
12
  isPlainObject,
1✔
13
  isSpecialRequestBody,
1✔
14
  newInstance,
1✔
15
  noop,
1✔
16
  sloughFunction
1✔
17
} from '@alova/shared/function';
1✔
18
import {
1✔
19
  MEMORY,
1✔
20
  PromiseCls,
1✔
21
  STORAGE_RESTORE,
1✔
22
  deleteAttr,
1✔
23
  falseValue,
1✔
24
  filterItem,
1✔
25
  mapItem,
1✔
26
  objectKeys,
1✔
27
  promiseFinally,
1✔
28
  promiseReject,
1✔
29
  promiseThen,
1✔
30
  trueValue,
1✔
31
  undefinedValue
1✔
32
} from '@alova/shared/vars';
1✔
33
import {
1✔
34
  AlovaGenerics,
1✔
35
  AlovaRequestAdapter,
1✔
36
  Arg,
1✔
37
  Method,
1✔
38
  ProgressUpdater,
1✔
39
  RespondedHandler,
1✔
40
  ResponseCompleteHandler,
1✔
41
  ResponseErrorHandler
1✔
42
} from '~/typings';
1✔
43
import { hitCacheBySource } from './manipulateCache';
1✔
44

1✔
45
// 请求适配器返回信息暂存,用于实现请求共享
1✔
46
type RequestAdapterReturnType = ReturnType<AlovaRequestAdapter<any, any, any>>;
1✔
47
const adapterReturnMap: Record<string, Record<string, RequestAdapterReturnType>> = {};
1✔
48

1✔
49
/**
1✔
50
 * 构建完整的url
1✔
51
 * @param base baseURL
1✔
52
 * @param url 路径
1✔
53
 * @param params url参数
1✔
54
 * @returns 完整的url
1✔
55
 */
1✔
56
const buildCompletedURL = (baseURL: string, url: string, params: Arg) => {
1✔
57
  // baseURL如果以/结尾,则去掉/
53✔
58
  baseURL = baseURL.endsWith('/') ? baseURL.slice(0, -1) : baseURL;
53✔
59
  // 如果不是/或http协议开头的,则需要添加/
53✔
60

53✔
61
  // Compatible with some RESTful usage
53✔
62
  // fix: https://github.com/alovajs/alova/issues/382
53✔
63
  if (url !== '') {
53✔
64
    url = url.match(/^(\/|https?:\/\/)/) ? url : `/${url}`;
52!
65
  }
52✔
66

53✔
67
  const completeURL = baseURL + url;
53✔
68

53✔
69
  // 将params对象转换为get字符串
53✔
70
  // 过滤掉值为undefined的
53✔
71
  const paramsStr = mapItem(
53✔
72
    filterItem(objectKeys(params), key => params[key] !== undefinedValue),
53✔
73
    key => `${key}=${params[key]}`
53✔
74
  ).join('&');
53✔
75
  // 将get参数拼接到url后面,注意url可能已存在参数
53✔
76
  return paramsStr
53✔
77
    ? +completeURL.includes('?')
53✔
78
      ? `${completeURL}&${paramsStr}`
26✔
79
      : `${completeURL}?${paramsStr}`
26✔
80
    : completeURL;
53✔
81
};
53✔
82

1✔
83
/**
1✔
84
 * 实际的请求函数
1✔
85
 * @param method 请求方法对象
1✔
86
 * @param forceRequest 忽略缓存
1✔
87
 * @returns 响应数据
1✔
88
 */
1✔
89
export default function sendRequest<AG extends AlovaGenerics>(methodInstance: Method<AG>, forceRequest: boolean) {
73✔
90
  let fromCache = trueValue;
73✔
91
  let requestAdapterCtrlsPromiseResolveFn: (value?: RequestAdapterReturnType) => void;
73✔
92
  const requestAdapterCtrlsPromise = newInstance(PromiseCls, resolve => {
73✔
93
    requestAdapterCtrlsPromiseResolveFn = resolve;
73✔
94
  }) as Promise<RequestAdapterReturnType | undefined>;
73✔
95
  const response = async () => {
73✔
96
    const { beforeRequest = noop, responded, requestAdapter, cacheLogger } = getOptions(methodInstance);
73✔
97
    const methodKey = getMethodInternalKey(methodInstance);
73✔
98

73✔
99
    const { s: toStorage, t: tag, m: cacheMode, e: expireMilliseconds } = getLocalCacheConfigParam(methodInstance);
73✔
100
    const { id, l1Cache, l2Cache, snapshots } = getContext(methodInstance);
73✔
101
    // 获取受控缓存或非受控缓存
73✔
102
    const { cacheFor } = getConfig(methodInstance);
73✔
103
    const { hitSource: methodHitSource } = methodInstance;
73✔
104

73✔
105
    // 如果当前method设置了受控缓存,则看是否有自定义的数据
73✔
106
    let cachedResponse = await (isFn(cacheFor)
73✔
107
      ? cacheFor()
73✔
108
      : // 如果是强制请求的,则跳过从缓存中获取的步骤
73✔
109
        // 否则判断是否使用缓存数据
68✔
110
        forceRequest
68✔
111
        ? undefinedValue
68✔
112
        : getWithCacheAdapter(id, methodKey, l1Cache));
73✔
113

71✔
114
    // 如果是STORAGE_RESTORE模式,且缓存没有数据时,则需要将持久化数据恢复到缓存中,过期时间要使用缓存的
71✔
115
    if (cacheMode === STORAGE_RESTORE && !cachedResponse) {
73✔
116
      const rawL2CacheData = await getRawWithCacheAdapter(id, methodKey, l2Cache, tag);
3✔
117
      if (rawL2CacheData) {
3!
118
        const [l2Response, l2ExpireMilliseconds] = rawL2CacheData;
×
119
        await setWithCacheAdapter(id, methodKey, l2Response, l2ExpireMilliseconds, l1Cache, methodHitSource);
×
120
        cachedResponse = l2Response;
×
121
      }
×
122
    }
3✔
123

71✔
124
    // 克隆method作为参数传给beforeRequest,防止多次使用原method实例请求时产生副作用
71✔
125
    // 放在` let cachedResponse = await ...`之后,解决在method.send中先赋值promise给method实例的问题,否则在clonedMethod中promise为undefined
71✔
126
    const clonedMethod = cloneMethod(methodInstance);
71✔
127

71✔
128
    // 发送请求前调用钩子函数
71✔
129
    // beforeRequest支持同步函数和异步函数
71✔
130
    await beforeRequest(clonedMethod);
71✔
131
    const { baseURL, url: newUrl, type, data } = clonedMethod;
69✔
132
    const { params = {}, headers = {}, transform = $self, shareRequest } = getConfig(clonedMethod);
69✔
133
    const namespacedAdapterReturnMap = (adapterReturnMap[id] = adapterReturnMap[id] || {});
73✔
134
    let requestAdapterCtrls = namespacedAdapterReturnMap[methodKey];
73✔
135
    let responseSuccessHandler: RespondedHandler<AG> = $self;
73✔
136
    let responseErrorHandler: ResponseErrorHandler<AG> | undefined = undefinedValue;
73✔
137
    let responseCompleteHandler: ResponseCompleteHandler<AG> = noop;
73✔
138

73✔
139
    // uniform handler of onSuccess, onError, onComplete
73✔
140
    if (isFn(responded)) {
73✔
141
      responseSuccessHandler = responded;
51✔
142
    } else if (isPlainObject(responded)) {
54✔
143
      const { onSuccess: successHandler, onError: errorHandler, onComplete: completeHandler } = responded;
15✔
144
      responseSuccessHandler = isFn(successHandler) ? successHandler : responseSuccessHandler;
15✔
145
      responseErrorHandler = isFn(errorHandler) ? errorHandler : responseErrorHandler;
15✔
146
      responseCompleteHandler = isFn(completeHandler) ? completeHandler : responseCompleteHandler;
15✔
147
    }
15✔
148
    // 如果没有缓存则发起请求
69✔
149
    if (cachedResponse !== undefinedValue) {
73✔
150
      requestAdapterCtrlsPromiseResolveFn(); // 遇到缓存将不传入ctrls
15✔
151

15✔
152
      // 打印缓存日志
15✔
153
      sloughFunction(cacheLogger, defaultCacheLogger)(cachedResponse, clonedMethod as any, cacheMode, tag);
15✔
154
      responseCompleteHandler(clonedMethod);
15✔
155
      return cachedResponse;
15✔
156
    }
15✔
157
    fromCache = falseValue;
54✔
158

54✔
159
    if (!shareRequest || !requestAdapterCtrls) {
73✔
160
      // 请求数据
53✔
161
      const ctrls = requestAdapter(
53✔
162
        {
53✔
163
          url: buildCompletedURL(baseURL, newUrl, params),
53✔
164
          type,
53✔
165
          data,
53✔
166
          headers
53✔
167
        },
53✔
168
        clonedMethod as any
53✔
169
      );
53✔
170
      requestAdapterCtrls = namespacedAdapterReturnMap[methodKey] = ctrls;
53✔
171
    }
53✔
172
    // 将requestAdapterCtrls传到promise中供onDownload、onUpload及abort中使用
54✔
173
    requestAdapterCtrlsPromiseResolveFn(requestAdapterCtrls);
54✔
174

54✔
175
    /**
54✔
176
     * 处理响应任务,失败时不缓存数据
54✔
177
     * @param responsePromise 响应promise实例
54✔
178
     * @param responseHeaders 请求头
54✔
179
     * @param callInSuccess 是否在成功回调中调用
54✔
180
     * @returns 处理后的response
54✔
181
     */
54✔
182
    const handleResponseTask = async (handlerReturns: any, responseHeaders: any, callInSuccess = trueValue) => {
54✔
183
      const responseData = await handlerReturns;
44✔
184
      const transformedData = await transform(responseData, responseHeaders || {});
44✔
185
      snapshots.save(methodInstance);
42✔
186

42✔
187
      // 即使缓存操作失败,也正常返回响应结构,避免因缓存操作问题导致请求错误
42✔
188
      // 缓存操作结果,可通过`cacheAdapter.emitter.on('success' | 'fail', event => {})`监听获取
42✔
189
      try {
42✔
190
        // 自动失效缓存
42✔
191
        await hitCacheBySource(clonedMethod);
42✔
192
      } catch (error) {}
44!
193

42✔
194
      // 当requestBody为特殊数据时不保存缓存
42✔
195
      // 原因1:特殊数据一般是提交特殊数据,需要和服务端交互
42✔
196
      // 原因2:特殊数据不便于生成缓存key
42✔
197
      const requestBody = clonedMethod.data;
42✔
198
      const toCache = !requestBody || !isSpecialRequestBody(requestBody);
44✔
199

44✔
200
      // 使用响应后最新的过期时间来缓存数据,避免因响应时间过长导致过期时间流失的问题
44✔
201
      if (toCache && callInSuccess) {
44✔
202
        try {
39✔
203
          await PromiseCls.all([
39✔
204
            setWithCacheAdapter(id, methodKey, transformedData, expireMilliseconds(MEMORY), l1Cache, methodHitSource),
39✔
205
            toStorage &&
39✔
206
              setWithCacheAdapter(
3✔
207
                id,
3✔
208
                methodKey,
3✔
209
                transformedData,
3✔
210
                expireMilliseconds(STORAGE_RESTORE),
3✔
211
                l2Cache,
3✔
212
                methodHitSource,
3✔
213
                tag
3✔
214
              )
39✔
215
          ]);
39✔
216
        } catch (error) {}
39!
217
      }
39✔
218
      return transformedData;
42✔
219
    };
54✔
220

54✔
221
    return promiseFinally(
54✔
222
      promiseThen(
54✔
223
        PromiseCls.all([requestAdapterCtrls.response(), requestAdapterCtrls.headers()]),
54✔
224
        ([rawResponse, rawHeaders]) => {
54✔
225
          // 无论请求成功、失败,都需要首先移除共享的请求
41✔
226
          deleteAttr(namespacedAdapterReturnMap, methodKey);
41✔
227
          return handleResponseTask(responseSuccessHandler(rawResponse, clonedMethod), rawHeaders);
41✔
228
        },
54✔
229
        (error: any) => {
54✔
230
          // 无论请求成功、失败,都需要首先移除共享的请求
13✔
231
          deleteAttr(namespacedAdapterReturnMap, methodKey);
13✔
232
          return isFn(responseErrorHandler)
13✔
233
            ? // 响应错误时,如果未抛出错误也将会处理响应成功的流程,但不缓存数据
13✔
234
              handleResponseTask(responseErrorHandler(error, clonedMethod), undefinedValue, falseValue)
5✔
235
            : promiseReject(error);
13✔
236
        }
13✔
237
      ),
54✔
238
      () => {
54✔
239
        responseCompleteHandler(clonedMethod);
54✔
240
      }
54✔
241
    );
54✔
242
  };
73✔
243

73✔
244
  return {
73✔
245
    // 请求中断函数
73✔
246
    abort: () => {
73✔
247
      promiseThen(
2✔
248
        requestAdapterCtrlsPromise,
2✔
249
        requestAdapterCtrls => requestAdapterCtrls && requestAdapterCtrls.abort()
2✔
250
      );
2✔
251
    },
73✔
252
    onDownload: (handler: ProgressUpdater) => {
73✔
253
      promiseThen(
1✔
254
        requestAdapterCtrlsPromise,
1✔
255
        requestAdapterCtrls =>
1✔
256
          requestAdapterCtrls && requestAdapterCtrls.onDownload && requestAdapterCtrls.onDownload(handler)
1✔
257
      );
1✔
258
    },
73✔
259
    onUpload: (handler: ProgressUpdater) => {
73✔
260
      promiseThen(
1✔
261
        requestAdapterCtrlsPromise,
1✔
262
        requestAdapterCtrls =>
1✔
263
          requestAdapterCtrls && requestAdapterCtrls.onUpload && requestAdapterCtrls.onUpload(handler)
1✔
264
      );
1✔
265
    },
73✔
266
    response,
73✔
267
    fromCache: () => fromCache
73✔
268
  };
73✔
269
}
73✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc