310 lines
8.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* eslint-disable no-console */
/* eslint-disable max-len */
/* eslint-disable object-curly-newline */
import { isJSFunction } from '@alilc/lowcode-types';
import { transformArrayToMap, transformStringToFunction } from './common';
import { jsonp, request, get, post } from './request';
import logger from './logger';
import { DataSource, DataSourceItem, IRendererAppHelper } from '../types';
const DS_STATUS = {
INIT: 'init',
LOADING: 'loading',
LOADED: 'loaded',
ERROR: 'error',
};
type DataSourceType = 'fetch' | 'jsonp';
/**
* do request for standard DataSourceType
* @param {DataSourceType} type type of DataSourceItem
* @param {any} options
*/
export function doRequest(type: DataSourceType, options: any) {
// eslint-disable-next-line prefer-const
let { uri, url, method = 'GET', headers, params, ...otherProps } = options;
otherProps = otherProps || {};
if (type === 'jsonp') {
return jsonp(uri, params, otherProps);
}
if (type === 'fetch') {
switch (method.toUpperCase()) {
case 'GET':
return get(uri, params, headers, otherProps);
case 'POST':
return post(uri, params, headers, otherProps);
default:
return request(uri, method, params, headers, otherProps);
}
}
logger.log(`Engine default dataSource does not support type:[${type}] dataSource request!`, options);
}
// TODO: according to protocol, we should implement errorHandler/shouldFetch/willFetch/requestHandler and isSync controll.
export class DataHelper {
/**
* host object that will be "this" object when excuting dataHandler
*
* @type {*}
* @memberof DataHelper
*/
host: any;
/**
* data source config
*
* @type {DataSource}
* @memberof DataHelper
*/
config: DataSource;
/**
* a parser function which will be called to process config data
* which eventually will call common/utils.processData() to process data
* (originalConfig) => parsedConfig
* @type {*}
* @memberof DataHelper
*/
parser: any;
/**
* config.list
*
* @type {any[]}
* @memberof DataHelper
*/
ajaxList: any[];
ajaxMap: any;
dataSourceMap: any;
appHelper: IRendererAppHelper;
constructor(comp: any, config: DataSource, appHelper: IRendererAppHelper, parser: any) {
this.host = comp;
this.config = config || {};
this.parser = parser;
this.ajaxList = config?.list || [];
this.ajaxMap = transformArrayToMap(this.ajaxList, 'id');
this.dataSourceMap = this.generateDataSourceMap();
this.appHelper = appHelper;
}
// 更新config只会更新配置状态保存
updateConfig(config = {}) {
this.config = config as DataSource;
this.ajaxList = (config as DataSource)?.list || [];
const ajaxMap: any = transformArrayToMap(this.ajaxList, 'id');
// 删除已经移除的接口
Object.keys(this.ajaxMap).forEach((key) => {
if (!ajaxMap[key]) {
delete this.dataSourceMap[key];
}
});
this.ajaxMap = ajaxMap;
// 添加未加入到dataSourceMap中的接口
this.ajaxList.forEach((item) => {
if (!this.dataSourceMap[item.id]) {
this.dataSourceMap[item.id] = {
status: DS_STATUS.INIT,
load: (...args: any) => {
// @ts-ignore
return this.getDataSource(item.id, ...args);
},
};
}
});
return this.dataSourceMap;
}
generateDataSourceMap() {
const res: any = {};
this.ajaxList.forEach((item) => {
res[item.id] = {
status: DS_STATUS.INIT,
load: (...args: any) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return this.getDataSource(item.id, ...args);
},
};
});
return res;
}
updateDataSourceMap(id: string, data: any, error: any) {
this.dataSourceMap[id].error = error || undefined;
this.dataSourceMap[id].data = data;
this.dataSourceMap[id].status = error ? DS_STATUS.ERROR : DS_STATUS.LOADED;
}
/**
* get all dataSourceItems which marked as isInit === true
* @private
* @returns
* @memberof DataHelper
*/
getInitDataSourseConfigs() {
const initConfigs = this.parser(this.ajaxList).filter((item: DataSourceItem) => {
// according to [spec](https://lowcode-engine.cn/lowcode), isInit should be boolean true to be working
if (item.isInit === true) {
this.dataSourceMap[item.id].status = DS_STATUS.LOADING;
return true;
}
return false;
});
return initConfigs;
}
/**
* process all dataSourceItems which marked as isInit === true, and get dataSource request results.
* @public
* @returns
* @memberof DataHelper
*/
getInitData() {
const initSyncData = this.getInitDataSourseConfigs();
// 所有 datasource 的 datahandler
return this.asyncDataHandler(initSyncData).then((res) => {
const { dataHandler } = this.config;
return this.handleData(null, dataHandler, res, null);
});
}
getDataSource(id: string, params: any, otherOptions: any, callback: any) {
const req = this.parser(this.ajaxMap[id]);
const options = req.options || {};
let callbackFn = callback;
let otherOptionsObj = otherOptions;
if (typeof otherOptions === 'function') {
callbackFn = otherOptions;
otherOptionsObj = {};
}
const { headers, ...otherProps } = otherOptionsObj || {};
if (!req) {
console.warn(`getDataSource API named ${id} not exist`);
return;
}
return this.asyncDataHandler([
{
...req,
options: {
...options,
// 支持参数为array的情况当参数为array时不做参数合并
params:
Array.isArray(options.params) || Array.isArray(params)
? params || options.params
: {
...options.params,
...params,
},
headers: {
...options.headers,
...headers,
},
...otherProps,
},
},
])
.then((res: any) => {
try {
callbackFn && callbackFn(res && res[id]);
} catch (e) {
console.error('load请求回调函数报错', e);
}
return res && res[id];
})
.catch((err) => {
try {
callbackFn && callbackFn(null, err);
} catch (e) {
console.error('load请求回调函数报错', e);
}
return err;
});
}
asyncDataHandler(asyncDataList: any[]) {
return new Promise((resolve, reject) => {
const allReq: any[] = [];
asyncDataList.forEach((req) => {
const { id, type } = req;
// TODO: need refactoring to remove 'legao' related logic
if (!id || !type || type === 'legao') {
return;
}
allReq.push(req);
});
if (allReq.length === 0) {
resolve({});
}
const res: any = {};
Promise.all(
allReq.map((item: any) => {
return new Promise((innerResolve) => {
const { type, id, dataHandler, options } = item;
const fetchHandler = (data: any, error: any) => {
res[id] = this.handleData(id, dataHandler, data, error);
this.updateDataSourceMap(id, res[id], error);
innerResolve({});
};
const doFetch = (innerType: string, innerOptions: any) => {
doRequest(innerType as any, innerOptions)
?.then((data: any) => {
fetchHandler(data, undefined);
})
.catch((err: Error) => {
fetchHandler(undefined, err);
});
};
this.dataSourceMap[id].status = DS_STATUS.LOADING;
doFetch(type, options);
});
}),
).then(() => {
resolve(res);
}).catch((e) => {
reject(e);
});
});
}
/**
* process data using dataHandler
*
* @param {(string | null)} id request id, will be used in error message, can be null
* @param {*} dataHandler
* @param {*} data
* @param {*} error
* @returns
* @memberof DataHelper
*/
handleData(id: string | null, dataHandler: any, data: any, error: any) {
let dataHandlerFun = dataHandler;
if (isJSFunction(dataHandler)) {
dataHandlerFun = transformStringToFunction(dataHandler.value);
}
if (!dataHandlerFun || typeof dataHandlerFun !== 'function') {
return data;
}
try {
return dataHandlerFun.call(this.host, data, error);
} catch (e) {
if (id) {
console.error(`[${id}]单个请求数据处理函数运行出错`, e);
} else {
console.error('请求数据处理函数运行出错', e);
}
}
}
}