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

alovajs / alova / #213

08 Oct 2024 08:21AM UTC coverage: 93.87% (+0.04%) from 93.826%
#213

push

github

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

ci: release

1612 of 1763 branches covered (91.44%)

Branch coverage included in aggregate %.

9551 of 10129 relevant lines covered (94.29%)

61.41 hits per line

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

97.28
/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如果以/结尾,则去掉/
61✔
58
  baseURL = baseURL.endsWith('/') ? baseURL.slice(0, -1) : baseURL;
61✔
59
  // 如果不是/或http协议开头的,则需要添加/
61✔
60

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

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

61✔
69
  // 将params对象转换为get字符串
61✔
70
  // 过滤掉值为undefined的
61✔
71
  const paramsStr = mapItem(
61✔
72
    filterItem(objectKeys(params), key => params[key] !== undefinedValue),
61✔
73
    key => `${key}=${params[key]}`
61✔
74
  ).join('&');
61✔
75
  // 将get参数拼接到url后面,注意url可能已存在参数
61✔
76
  return paramsStr
61✔
77
    ? +completeURL.includes('?')
61✔
78
      ? `${completeURL}&${paramsStr}`
24✔
79
      : `${completeURL}?${paramsStr}`
24✔
80
    : completeURL;
61✔
81
};
61✔
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) {
82✔
90
  let fromCache = trueValue;
82✔
91
  let requestAdapterCtrlsPromiseResolveFn: (value?: RequestAdapterReturnType) => void;
82✔
92
  const requestAdapterCtrlsPromise = newInstance(PromiseCls, resolve => {
82✔
93
    requestAdapterCtrlsPromiseResolveFn = resolve;
82✔
94
  }) as Promise<RequestAdapterReturnType | undefined>;
82✔
95
  const response = async () => {
82✔
96
    const { beforeRequest = noop, responded, requestAdapter, cacheLogger } = getOptions(methodInstance);
82✔
97
    const methodKey = getMethodInternalKey(methodInstance);
82✔
98

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

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

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

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

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

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

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

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

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

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

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

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

66✔
221
    return promiseFinally(
66✔
222
      promiseThen(
66✔
223
        PromiseCls.all([requestAdapterCtrls.response(), requestAdapterCtrls.headers()]),
66✔
224
        ([rawResponse, rawHeaders]) => {
66✔
225
          // 无论请求成功、失败,都需要首先移除共享的请求
53✔
226
          deleteAttr(namespacedAdapterReturnMap, methodKey);
53✔
227
          return handleResponseTask(responseSuccessHandler(rawResponse, clonedMethod), rawHeaders);
53✔
228
        },
66✔
229
        (error: any) => {
66✔
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
      ),
66✔
238
      () => {
66✔
239
        responseCompleteHandler(clonedMethod);
66✔
240
      }
66✔
241
    );
66✔
242
  };
82✔
243

82✔
244
  return {
82✔
245
    // 请求中断函数
82✔
246
    abort: () => {
82✔
247
      promiseThen(
2✔
248
        requestAdapterCtrlsPromise,
2✔
249
        requestAdapterCtrls => requestAdapterCtrls && requestAdapterCtrls.abort()
2✔
250
      );
2✔
251
    },
82✔
252
    onDownload: (handler: ProgressUpdater) => {
82✔
253
      promiseThen(
1✔
254
        requestAdapterCtrlsPromise,
1✔
255
        requestAdapterCtrls =>
1✔
256
          requestAdapterCtrls && requestAdapterCtrls.onDownload && requestAdapterCtrls.onDownload(handler)
1✔
257
      );
1✔
258
    },
82✔
259
    onUpload: (handler: ProgressUpdater) => {
82✔
260
      promiseThen(
1✔
261
        requestAdapterCtrlsPromise,
1✔
262
        requestAdapterCtrls =>
1✔
263
          requestAdapterCtrls && requestAdapterCtrls.onUpload && requestAdapterCtrls.onUpload(handler)
1✔
264
      );
1✔
265
    },
82✔
266
    response,
82✔
267
    fromCache: () => fromCache
82✔
268
  };
82✔
269
}
82✔
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