1 实际项目中使用Web Worker
下面这篇文章详细介绍了web worker常用的api, 场景(这里不知道有没有具体衡量指标),使用过程中的注意事项,文章写的很好,感兴趣可以看下,这里不在详细叙述具体概念
Web Worker使用
1.1 场景
这里分享的一个场景,是把从数据查询接口获取的数据的格式化过程放在web worker中执行。
首先简单介绍下我们的项目,一个可视化相关项目,接口主要分为两大方面,配置查询和数据查询接口,其中数据查询接口是影响页面性能指标的一个很关键的因素,并且从查询接口获取到数据之后的格式化过程逻辑全部放在fe中,数据格式化这个过程可能涉及一些比较大的计算,因此我们选择将它拆出来放在一个单独web worker线程中,减少对主线程造成影响。
1.2 实现
具体解释看注释
// workerHelper.ts
import Worker from 'worker-loader?inline=no-fallback!./query.worker.ts';
import axios, { CancelTokenSource } from 'axios';
import { ErrorType } from '@/api';
const { CancelToken } = axios;
type EventHanler = {
resolve: (value: any) => void;
reject: (reason?: any) => void;
};
const requestMap = new Map<string, CancelTokenSource>();
// 调用接口
const query = async (
isShared: boolean,
type: ComponentType,
source: VISUAL_TYPE,
resourceId: string | number,
params,
sharedComponentQueryResult,
horusParams,
skipCancel: boolean = false,
// eslint-disable-next-line max-params
) => {
let resultData;
await axios.post(`******`, params, {
headers: {},
cancelToken: cancelToken.token,
})
.then(res => {
if (res.status === 200 && res.data.code === 0) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
resultData = res.data.data;
} else if (res.status === 404) {
// eslint-disable-next-line no-throw-literal
throw 'request 404';
} else {
// eslint-disable-next-line no-throw-literal
throw {
message: res.data.msg || res.statusText || '',
logId,
};
}
})
.catch(err => {
// 不是手动取消
const errType = err.__CANCEL__
? ErrorType.CANCEL
: (err.message ?? err)?.includes('timeout')
? ErrorType.TIMEOUT
: ErrorType.REQUEST;
const message = errType === ErrorType.TIMEOUT ? 'request timeout' : err.message ?? err;
console.log(message, 'message');
const logId = err.logId ?? '';
// eslint-disable-next-line no-throw-literal
throw {
message,
type: errType,
logId,
};
});
return resultData;
};
export default class WorkerHelper {
private static _worker: Worker | null; // 定义一个worker
// 初始化
static init() {
if (!this._worker) {
// 注册一个数据格式化worker
this._worker = new Worker();
// webworker异常
this._worker.addEventListener('error', e => {
throw e;
});
}
}
// 查询函数,对外提供
static query<T = any>(
isShared: boolean,
source: VISUAL_TYPE,
resourceId: string,
type: ComponentType,
params: any,
sharedComponentQueryResult: any,
skipCancel: boolean = false,
): Promise<T> {
return new Promise((resolve, reject) => {
// 如果未初始化,初始化
this.init();
query(isShared, type, source, resourceId, params, sharedComponentQueryResult, skipCancel)
.then(data => {
if (
type !== ComponentType.RICH_TEXT
&& type !== ComponentType.TABLE
&& type !== ComponentType.COCKPIT_RICH_TEXT
) {
// 查询到数据之后给worker发送消息启动格式化数据线程
this._worker?.postMessage({
method: 'format',
options: {
id,
data,
type,
params,
lang: getCurrentLang()
},
});
} else {
this.messageHandler(id, data);
}
})
.catch((e: Error) => {
this.messageHandler(id, null, e);
});
});
}
/**
*
* @param id 组件id
*/
static cancelQuery(id: string) {
requestMap.get(id as string)?.cancel();
}
/**
*
* @param 批量取消
*/
static cancelMoreQuery(ids: string[]) {
for(let id of ids){
requestMap.get(id as string)?.cancel();
}
}
// 终止worker
static terminate() {
this._worker?.terminate();
this._worker = null;
}
}
// 格式化worker
// query.worker.ts
interface QueryParams {
isShared: boolean;
source: VISUAL_TYPE;
resourceId: string;
type: ComponentType;
id: number;
params: any;
sharedComponentQueryResult: any;
baseUrl: string;
horusParams: any;
}
interface CancelParams {
id: string;
}
const ctx: Worker = self as any;
const formatHandler = async options => {
const { data, id, type, params, lang } = options;
let result, error;
try {
if (!id) {
error = new Error('id 不存在');
throw error;
}
switch (type) {
case ComponentType.MUTI_METRIC_CARD:
result = transformMultiMetricCard(data, params, lang);
break;
case ComponentType.SINGLE_METRIC_CARD:
result = transformSingleMetricCard(data, params, lang);
break;
case ComponentType.PROCESS:
result = formatProgress(data, params);
break;
case ComponentType.LINE:
result = formatLine(data, params);
break;
case ComponentType.PIE:
result = formatPie(data, params);
break;
case ComponentType.BAR:
result = formatBar(data, params);
break;
case ComponentType.BAR_ROW:
result = formatBarRow(data, params);
break;
case ComponentType.TABLE:
case ComponentType.COCKPIT_RICH_TEXT:
case ComponentType.RICH_TEXT:
default:
break;
}
} catch (e) {
error = e;
} finally {
ctx.postMessage({ id, data: result, error });
}
};
ctx.addEventListener(
'message',
(
event: MessageEvent<{
method: 'format' | 'cancelQuery';
options: QueryParams | CancelParams;
}>
) => {
const {
data: { method, options },
} = event;
if (method === 'format') {
// 格式化数据
formatHandler(options);
}
}
);
// 具体使用
import WorkerHelper from '@/utils/workerHelper';
...
const queryData = async (isShared, type, resourceId, params, sharedComponentQueryResult) => {
isLoading.value = true;
const { data, series, delayedMessage, feQueryConfig, percent: percentConfig } = await WorkerHelper.query(
isShared,
type,
resourceId,
ComponentType.BAR,
params,
sharedComponentQueryResult
);
...
};
...