装饰器定义函数
decorate.js
function isDescriptor (desc) {
if (!desc || !desc.hasOwnProperty) {
return false;
}
const keys = ['value', 'initializer', 'get', 'set'];
for (let i = 0, l = keys.length; i < l; i++) {
if (desc.hasOwnProperty(keys[i])) {
return true;
}
}
return false;
}
export const decorate = function (handleDescriptor) {
return function (...entryArgs) {
if (isDescriptor(entryArgs[entryArgs.length - 1])) {
return handleDescriptor(...entryArgs);
} else {
return function () {
handleDescriptor(...arguments, ...entryArgs);
};
}
};
};
export const runBefore = decorate(function (target, name, descriptor, handle) {
const raw = descriptor.value;
descriptor.value = async function () {
try {
let flag = await target[handle](arguments);
return await raw.apply(target, [flag, ...arguments]);
} catch (err) {
console.log(err);
}
};
});
export const runAfter = decorate(function (target, name, descriptor, handle) {
const raw = descriptor.value;
descriptor.value = async function () {
try {
let currentReturn = await raw.apply(target, arguments);
return await target[handle]([currentReturn, ...arguments]);
} finally {
// target[handle]();
}
};
});
装饰器使用例子
前端eslint配置要求使用驼峰,而后端java同学采用下划线来定义对象,同时采用下划线返回。
api.js 用来配置 ajax 接口, 发送前,将驼峰转小写;回来后,将小写转驼峰。
其实这里,类似于pipe管道模式
import {runAfter, runBefore} from './decorate';
type SubjectQueryModel = {
pageNum: number;
pageSize: number;
keyword?: string;
}
export class MyApi{
private httpClient: IHttpClient;
constructor(httpClient: IHttpClient, authToken?: string) {
if (authToken) {
httpClient.setAuthToken(authToken);
}
this.httpClient = httpClient;
}
@runBefore('toUnderScore')
@runAfter('toCamelcase')
public async getSubjectList(condition: SubjectQueryModel, ...args: any[]): Promise<SubjectModel[]> {
console.log('current arguments', args);
const currentParams = args[0];
console.log('currentParams', currentParams);
return {
total: 0,
records: [],
size: 10,
department_ids: [1, 2, 3],
};
// return this.httpClient.restPost<SubjectQueryModel, SubjectModel[]>('/subjects/listfororg', condition, 3, 10000);
}
// 装饰器 下划线 转驼峰
public toCamelcase () {
// toHump
console.log('after arguments', arguments);
let res = arguments[0][0];
return this.convertObjectKeysToCamelcase(res);
}
// 装饰器 驼峰转 下划线
public toUnderScore () {
console.log('before arguments', arguments);
let params = arguments[0][0];
let newParam = this.convertObjectKeysToUnderscore(params);
console.log('newParams', newParam);
return newParam;
}
// 实际驼峰转 下划线
public convertObjectKeysToUnderscore(obj: any) {
const result = {};
for (const [key, value] of Object.entries(obj)) {
let newKey = key.replace(/([A-Z])/g, '_$1').toLowerCase();
if (typeof value === 'object' && value !== null) {
result[newKey] = this.convertObjectKeysToUnderscore(value);
} else {
result[newKey] = value;
}
}
return result;
}
// 实际下划线 转 驼峰
public convertObjectKeysToCamelcase(obj: any) {
const result = {};
for (const [ key, value ] of Object.entries(obj)) {
let newKey = key.replace(/[_][a-z]/g, (match) => match[1].toUpperCase());
if (typeof value === 'object' && value !== null) {
result[newKey] = this.convertObjectKeysToCamelcase(value);
} else {
result[newKey] = value;
}
}
return result;
}
}
结果: 使用时参数传入
{
pageNum: 1, // 实际发送时 pageNum => page_num
pageSize: 10, // pageSize => page_size
}
调用返回后
{
total: 100,
size: 10,
records: subjectModel[],
departmentIds: [], // 这里将 ajax返回后,装饰器将department_ids => departmentIds
}
类似的做法,可以做日志,错误监听;
log.js
import {decorate} from './decorate.js';
export const log = decorate(function (target, name, descriptor) {
const raw = descriptor.value
descriptor.value = async function () {
console.log(`[log]-${ target.constructor.name }.${ name }:input`, ...arguments)
const ret = await raw.apply(this, arguments)
console.log(`[log]-${ target.constructor.name }.${ name }:output`, ret)
return ret
}
})
handleError.js
import {decorate} from './decorate.js';
export const handleError = decorate(function (target, name, descriptor, {success, error} = {}) {
const raw = descriptor.value
descriptor.value = async function () {
try {
const ret = await raw.apply(this, arguments)
return ret
} catch (err) {
// 调用 logger.error(err)
return null/err
}
}
})
pipe.js
export default decorate(function (target, name, descriptor, pipes = []) {
pipes.reverse().forEach(handle => handle(target, name, descriptor))
const raw = descriptor.value
descriptor.value = function () {
return new Promise(async (resolve, reject) => {
try {
resolve(await raw.apply(this, arguments))
} catch (err) {
reject(err)
}
})
}
})