amis fetcher 的工作原理
input-tree 控件的 deleteApi 请求没有携带 data 属性。
deleteApi 使用的 delete 方法的请求,而 delete 的请求是不会有 data 属性的。
解决方法是修改成 post 方法或是在 url 中携带参数。
原理分析:
当在 amis 配置页面中使用 http 请求时,会使用全局配置的 fetcher 进行数据的请求与接收。
实际上配置的 fetcher 函数会在 amis 的框架内被封装处理。
js
// amis 3.3
// \amis\packages\amis-core\src\utils\api.ts
export function wrapFetcher(
fn: (config: fetcherConfig) => Promise<fetcherResult>,//这里的fn是客户配置的fetcher函数
tracker?: (eventTrack: EventTrack, data: any) => void
) {
// 避免重复处理
if ((fn as any)._wrappedFetcher) {
return fn as any;
}
const wrappedFetcher = async function (
api: Api,
data: object,//业务数据
options?: object
) {
//处理后的api参数
api = buildApi(api, data, options) as ApiObject;
if (api.requestAdaptor) {
debug('api', 'before requestAdaptor', api);
api = (await api.requestAdaptor(api, data)) || api;
debug('api', 'after requestAdaptor', api);
}
if (
api.data &&
(api.data instanceof FormData ||
hasFile(api.data) ||
api.dataType === 'form-data')
) {
api.data =
api.data instanceof FormData
? api.data
: object2formData(api.data, api.qsOptions);
} else if (
api.data &&
typeof api.data !== 'string' &&
api.dataType === 'form'
) {
api.data = qsstringify(api.data, api.qsOptions) as any;
api.headers = api.headers || (api.headers = {});
api.headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (
api.data &&
typeof api.data !== 'string' &&
api.dataType === 'json'
) {
api.data = JSON.stringify(api.data) as any;
api.headers = api.headers || (api.headers = {});
api.headers['Content-Type'] = 'application/json';
}
// 如果发送适配器中设置了 mockResponse
// 则直接跳过请求发送
if (api.mockResponse) {
return wrapAdaptor(Promise.resolve(api.mockResponse) as any, api, data);
}
if (!isValidApi(api.url)) {
throw new Error(`invalid api url:${api.url}`);
}
debug('api', 'request api', api);
tracker?.(
{eventType: 'api', eventData: omit(api, ['config', 'data', 'body'])},
api.data
);
if (api.method?.toLocaleLowerCase() === 'jsonp') {
return wrapAdaptor(jsonpFetcher(api), api, data);
}
if (api.method?.toLocaleLowerCase() === 'js') {
return wrapAdaptor(jsFetcher(fn, api), api, data);
}
if (typeof api.cache === 'number' && api.cache > 0) {
const apiCache = getApiCache(api);
return wrapAdaptor(
apiCache
? (apiCache as ApiCacheConfig).cachedPromise
: setApiCache(api, fn(api)),
api,
data
);
}
// IE 下 get 请求会被缓存,所以自动加个时间戳
if (isIE && api && api.method?.toLocaleLowerCase() === 'get') {
const timeStamp = `_t=${Date.now()}`;
if (api.url.indexOf('?') === -1) {
api.url = api.url + `?${timeStamp}`;
} else {
api.url = api.url + `&${timeStamp}`;
}
}
//在这里调用了原始的fetcher配置函数,而fetcher的参数api需要经过上面的处理。
return wrapAdaptor(fn(api), api, data);
};
(wrappedFetcher as any)._wrappedFetcher = true;
return wrappedFetcher;
}
非常重要的处理函数,封装了 api 请求的参数。
js
export function buildApi(
api: Api,//原始的请求参数
data?: object,//业务数据
options: {
autoAppend?: boolean;
ignoreData?: boolean;
[propName: string]: any;
} = {}
): ApiObject {
api = normalizeApi(api, options.method);
const {autoAppend, ignoreData, ...rest} = options;
//请求参数的config属性处理
api.config = {
...rest
};
api.method = (api.method || (options as any).method || 'get').toLowerCase();
//headers是否使用公式引用了data
if (api.headers) {
api.headers = dataMapping(api.headers, data, undefined, false);
}
if (api.requestAdaptor && typeof api.requestAdaptor === 'string') {
api.requestAdaptor = str2AsyncFunction(
api.requestAdaptor,
'api',
'context'
) as any;
}
if (api.adaptor && typeof api.adaptor === 'string') {
api.adaptor = str2AsyncFunction(
api.adaptor,
'payload',
'response',
'api',
'context'
) as any;
}
if (!data) {
return api;
} else if (
data instanceof FormData ||
data instanceof Blob ||
data instanceof ArrayBuffer
) {
//api参数中的data属性赋值
//只有传入数据是表单数据,数组,二进制文件才会传给api的data属性。
api.data = data;
return api;
}
const raw = (api.url = api.url || '');
let ast: any = undefined;
try {
ast = memoryParse(api.url);
} catch (e) {
console.warn(`api 配置语法出错:${e}`);
return api;
}
const url = ast.body
.map((item: any, index: number) => {
return item.type === 'raw' ? item.value : `__expression__${index}__`;
})
.join('');
const idx = url.indexOf('?');
let replaceExpression = (
fragment: string,
defaultFilter = 'url_encode',
defVal: any = undefined
) => {
return fragment.replace(
/__expression__(\d+)__/g,
(_: any, index: string) => {
return (
evaluate(ast.body[index], data, {
defaultFilter: defaultFilter
}) ?? defVal
);
}
);
};
// 是否过滤空字符串 query
const queryStringify = (query: any) =>
qsstringify(
query,
(api as ApiObject)?.filterEmptyQuery
? {
filter: (key: string, value: any) => {
return value === '' ? undefined : value;
}
}
: undefined
);
/** 追加data到请求的Query中 */
const attachDataToQuery = (
apiObject: ApiObject,
ctx: Record<string, any>,
merge: boolean
) => {
const idx = apiObject.url.indexOf('?');
if (~idx) {
const params = (apiObject.query = {
...qsparse(apiObject.url.substring(idx + 1)),
...apiObject.query,
...ctx
});
apiObject.url =
apiObject.url.substring(0, idx) + '?' + queryStringify(params);
} else {
apiObject.query = {...apiObject.query, ...ctx};
const query = queryStringify(merge ? apiObject.query : ctx);
if (query) {
apiObject.url = `${apiObject.url}?${query}`;
}
}
return apiObject;
};
if (~idx) {
const hashIdx = url.indexOf('#');
const params = qsparse(
url.substring(idx + 1, ~hashIdx && hashIdx > idx ? hashIdx : undefined)
);
// 将里面的表达式运算完
JSONTraverse(params, (value: any, key: string | number, host: any) => {
if (typeof value === 'string' && /^__expression__(\d+)__$/.test(value)) {
host[key] = evaluate(ast.body[RegExp.$1].body, data) ?? '';
} else if (typeof value === 'string') {
// 参数值里面的片段不能 url_encode 了,所以是不处理
host[key] = replaceExpression(host[key], 'raw', '');
}
});
const left = replaceExpression(url.substring(0, idx), 'raw', '');
// 追加
Object.assign(params, api.query);
api.url =
left +
(~left.indexOf('?') ? '&' : '?') +
queryStringify(
(api.query = dataMapping(params, data, undefined, api.convertKeyToPath))
) +
(~hashIdx && hashIdx > idx
? replaceExpression(url.substring(hashIdx))
: '');
} else {
api.url = replaceExpression(url, 'raw', '');
}
if (ignoreData) {
return api;
}
if (api.data) {
api.body = api.data = dataMapping(
api.data,
data,
undefined,
api.convertKeyToPath
);
} else if (
api.method === 'post' ||
api.method === 'put' ||
api.method === 'patch'
) {
//api body赋值,除了以上三种方法以外的方法data不会赋值给api.data
api.body = api.data = data;
}
// 给 query 做数据映射
if (api.query) {
api.query = dataMapping(api.query, data, undefined, api.convertKeyToPath);
}
// get 类请求,把 data 附带到 url 上。
if (api.method === 'get' || api.method === 'jsonp' || api.method === 'js') {
if (
!api.data &&
((!~raw.indexOf('$') && autoAppend) || api.forceAppendDataToQuery)
) {
api.data = data;
api.query = {
...api.query,
...data
};
} else if (
api.attachDataToQuery === false &&
api.data &&
((!~raw.indexOf('$') && autoAppend) || api.forceAppendDataToQuery)
) {
api = attachDataToQuery(api, data, false);
}
if (api.data && api.attachDataToQuery !== false) {
api = attachDataToQuery(api, api.data, true);
delete api.data;
}
}
// 非 get 类请求也可以携带参数到 url,只要 query 有值
else if (api.method) {
const idx = api.url.indexOf('?');
if (~idx) {
let params = (api.query = {
...qsparse(api.url.substring(idx + 1)),
...api.query
});
api.url = api.url.substring(0, idx) + '?' + queryStringify(params);
} else {
const query = queryStringify(api.query);
if (query) {
api.url = `${api.url}?${query}`;
}
}
}
if (api.graphql) {
if (api.method === 'get') {
api.query = api.data = {...api.query, query: api.graphql};
} else if (
api.method === 'post' ||
api.method === 'put' ||
api.method === 'patch'
) {
api.body = api.data = {
query: api.graphql,
operationName: api.operationName,
variables: cloneObject(api.data)
};
}
} else if (api.jsonql) {
api.method = 'post';
api.jsonql = dataMapping(
api.jsonql,
/** 需要上层数据域的内容 */
extendObject(data, {...api.query, ...data}, false),
undefined,
false,
true
);
/** 同时设置了JSONQL和data时走兼容场景 */
api.body = api.data =
api.data && api.jsonql
? {
data: api.data,
jsonql: api.jsonql
}
: api.jsonql;
/** JSONQL所有method需要追加data中的变量到query中 */
if (api.forceAppendDataToQuery) {
api = attachDataToQuery(api, data, true);
}
}
return api;
}