156 lines
4.5 KiB
TypeScript
156 lines
4.5 KiB
TypeScript
/*
|
||
* Copyright 2025 coze-dev Authors
|
||
*
|
||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
* you may not use this file except in compliance with the License.
|
||
* You may obtain a copy of the License at
|
||
*
|
||
* http://www.apache.org/licenses/LICENSE-2.0
|
||
*
|
||
* Unless required by applicable law or agreed to in writing, software
|
||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
* See the License for the specific language governing permissions and
|
||
* limitations under the License.
|
||
*/
|
||
|
||
import { type IOptions, normalizeRequest } from './utils';
|
||
import type { IMeta, CustomAPIMeta } from './types';
|
||
|
||
export interface ApiLike<T, K, O = unknown, B extends boolean = false> {
|
||
(req: T, option?: O extends object ? IOptions & O : IOptions): Promise<K>;
|
||
meta: IMeta;
|
||
/** fork 一份实例,该实例具有可中止请求的能力 */
|
||
withAbort: () => CancelAbleApi<T, K, O, B>;
|
||
}
|
||
|
||
export interface CancelAbleApi<T, K, O = unknown, B extends boolean = false>
|
||
extends ApiLike<T, K, O, B> {
|
||
// 中止请求
|
||
abort: () => void;
|
||
// 是否是取消
|
||
isAborted: () => boolean;
|
||
}
|
||
|
||
/**
|
||
* 自定义构建 api 方法
|
||
* @param meta
|
||
* @param cancelable
|
||
* @param useCustom
|
||
* @returns
|
||
*/
|
||
// eslint-disable-next-line max-params
|
||
export function createAPI<T extends {}, K, O = unknown, B extends boolean = false>(
|
||
meta: IMeta,
|
||
cancelable?: B,
|
||
useCustom = false,
|
||
customOption?: O extends object ? IOptions & O : IOptions,
|
||
): B extends false ? ApiLike<T, K, O, B> : CancelAbleApi<T, K, O, B> {
|
||
let abortController: AbortController | undefined;
|
||
let pending: undefined | boolean;
|
||
async function api(
|
||
req: T,
|
||
option: O extends object ? IOptions & O : IOptions,
|
||
): Promise<K> {
|
||
pending = true;
|
||
|
||
option = { ...(option || {}), ...customOption };
|
||
|
||
// 这里可以使用传进来的 req 作为默认映射,减少需要在 customAPI 中,需要手动绑定的情况
|
||
if (useCustom) {
|
||
const mappingKeys: string[] = Object.keys(meta.reqMapping)
|
||
.map(key => meta.reqMapping[key])
|
||
.reduce((a, b) => [...a, ...b], []);
|
||
const defaultFiled = Object.keys(req).filter(
|
||
field => !mappingKeys.includes(field),
|
||
);
|
||
|
||
if (['POST', 'PUT', 'PATCH'].includes(meta.method)) {
|
||
meta.reqMapping.body = [
|
||
...defaultFiled,
|
||
...(meta.reqMapping.body || []),
|
||
];
|
||
}
|
||
if (['GET', 'DELETE'].includes(meta.method)) {
|
||
meta.reqMapping.query = [
|
||
...defaultFiled,
|
||
...(meta.reqMapping.query || []),
|
||
];
|
||
}
|
||
}
|
||
|
||
const { client, uri, requestOption } = normalizeRequest(req, meta, option);
|
||
|
||
if (!abortController && cancelable) {
|
||
abortController = new AbortController();
|
||
}
|
||
if (abortController) {
|
||
requestOption.signal = abortController.signal;
|
||
}
|
||
|
||
try {
|
||
const res = await client(uri, requestOption, option);
|
||
return res;
|
||
} finally {
|
||
pending = false;
|
||
}
|
||
}
|
||
|
||
function abort() {
|
||
/**
|
||
* 这里加上 pending 状态的原因是,abortController.signal 的状态值只受控于 abortController.abort() 方法;
|
||
* 不管请求是否完成或者异常,只要调用 abortController.abort(), abortController.signal.aborted 必定为 true,
|
||
* 这样不好判断请求是否真 aborted;
|
||
*
|
||
* 这里改为,只有在请求 pending 的情况下,可执行 abort(),
|
||
* isAborted === true 时,请求异常必定是因为手动 abort 导致的
|
||
*/
|
||
if (pending === true && cancelable && abortController) {
|
||
abortController.abort();
|
||
}
|
||
}
|
||
|
||
function isAborted() {
|
||
return !!abortController?.signal.aborted;
|
||
}
|
||
|
||
function withAbort() {
|
||
return createAPI<T, K, O, true>(meta, true, useCustom, customOption);
|
||
}
|
||
|
||
api.meta = meta;
|
||
api.withAbort = withAbort;
|
||
if (cancelable) {
|
||
api.abort = abort;
|
||
api.isAborted = isAborted;
|
||
}
|
||
return api as any;
|
||
}
|
||
|
||
/**
|
||
* 一些非泛化的接口,可以使用改方法构建,方便统一管理接口
|
||
* @param customAPIMeta
|
||
* @param cancelable
|
||
* @returns
|
||
* @example
|
||
*
|
||
*/
|
||
export function createCustomAPI<
|
||
T extends {},
|
||
K,
|
||
O = unknown,
|
||
B extends boolean = false,
|
||
>(customAPIMeta: CustomAPIMeta, cancelable?: B) {
|
||
const name = `${customAPIMeta.method}_${customAPIMeta.url}`;
|
||
const meta: IMeta = {
|
||
...customAPIMeta,
|
||
reqMapping: customAPIMeta.reqMapping || {},
|
||
name,
|
||
service: 'CustomAPI',
|
||
schemaRoot: '',
|
||
reqType: `${name}_req`,
|
||
resType: `${name}_res`,
|
||
};
|
||
return createAPI<T, K, O, B>(meta, cancelable, true);
|
||
}
|