vue-i18n-extract-plugin 1.0.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js ADDED
@@ -0,0 +1,74 @@
1
+ const babelPluginI18n = require("./babel-plugin-i18n");
2
+ const { extractI18n, addI18nImportIfNeeded } = require("./extract");
3
+ const { defaultOptions } = require("./options");
4
+ const {
5
+ autoTranslate,
6
+ translateChunks,
7
+ createTextSplitter,
8
+ getLangJsonPath
9
+ } = require("./translate");
10
+ const translators = require("./translators");
11
+ const {
12
+ hashKey,
13
+ generateId,
14
+ parseArg,
15
+ checkAgainstRegexArray,
16
+ extractFunctionName,
17
+ relativeCWDPath,
18
+ shouldExtract,
19
+ trimEmptyLine,
20
+ padEmptyLine,
21
+ excludeDirectives,
22
+ EXCLUDED_CALL
23
+ } = require("./utils");
24
+ const {
25
+ shouldTransform,
26
+ isTFunction,
27
+ createI18nVisitor,
28
+ createI18nPlugin
29
+ } = require("./visitors");
30
+ const vitePluginI18n = require("./vite-plugin-i18n");
31
+ const vitePluginImportI18n = require("./vite-plugin-import-i18n");
32
+ const WebpackPluginI18n = require("./webpack-plugin-i18n");
33
+ const vueI18nLoader = require("./vue-i18n-loader");
34
+ const babelPluginImportI18n = require("./babel-plugin-import-i18n");
35
+ const {
36
+ i18nImportAstTransform,
37
+ i18nImportTransform,
38
+ extractScriptContent
39
+ } = require("./import-i18n-transform");
40
+
41
+ module.exports = {
42
+ babelPluginI18n,
43
+ extractI18n,
44
+ defaultOptions,
45
+ translators,
46
+ autoTranslate,
47
+ translateChunks,
48
+ createTextSplitter,
49
+ getLangJsonPath,
50
+ hashKey,
51
+ generateId,
52
+ parseArg,
53
+ checkAgainstRegexArray,
54
+ extractFunctionName,
55
+ relativeCWDPath,
56
+ shouldExtract,
57
+ trimEmptyLine,
58
+ padEmptyLine,
59
+ excludeDirectives,
60
+ EXCLUDED_CALL,
61
+ shouldTransform,
62
+ isTFunction,
63
+ addI18nImportIfNeeded,
64
+ createI18nVisitor,
65
+ createI18nPlugin,
66
+ vitePluginI18n,
67
+ vitePluginImportI18n,
68
+ WebpackPluginI18n,
69
+ vueI18nLoader,
70
+ babelPluginImportI18n,
71
+ i18nImportAstTransform,
72
+ i18nImportTransform,
73
+ extractScriptContent
74
+ };
package/lib/options.js ADDED
@@ -0,0 +1,39 @@
1
+ const { GoogleTranslator } = require("./translators");
2
+
3
+ const defaultOptions = {
4
+ translateKey: "$t", // 提取的函数的名称
5
+ rewrite: true, // 是否将提取到的内容转换为id后重写入源文件
6
+ extractFromText: true, // 是否允许从纯文本节点中提取翻译内容
7
+ autoImportI18n: true, // 是否自动导入 i18n 模块
8
+ autoTranslate: true, // 提取完成后是否自动翻译
9
+ cleanTranslate: true, // 是否清理无用的翻译内容
10
+ enableExtractInPlugin: true, // 是否在插件中自动提取翻译内容
11
+ outputJsonFileInPlugin: true, // 是否在插件中输出 JSON 文件
12
+ outputJsonFileDebounceTimeInPlugin: 2000, // 输出 JSON 文件的防抖时间
13
+ translateInterval: 1000, // 自动翻译的间隔时间
14
+ excludedCall: [], // 排除的调用函数名称数组
15
+ includePath: ["src/"], // 包含路径的数组
16
+ excludedPath: [], // 排除路径的数组
17
+ allowedExtensions: [".vue", ".tsx", ".ts", ".jsx", ".js"], // 允许提取的文件扩展名
18
+ fromLang: "zh-cn", // 源语言, 目前支持提取的语言有:zh-cn(zh-tw), en, ja, ko, ru
19
+ translateLangKeys: ["zh-tw", "en"], // 需要翻译为的语言键
20
+ i18nPkgImportPath: "@/i18n", // i18n语言包导入路径
21
+ outputPath: "src/i18n", // 提取的语言包输出文件路径
22
+ customGenLangFileName: langKey => langKey, // 自定义生成语言文件名
23
+ // 翻译后的文本处理函数,方便对翻译后的文本进行二次加工,如每个单词首字母大写, params: text: 翻译后的文本, toLang: 翻译后的目标语言,translateLangKeys的枚举成员
24
+ customTranslatedText: (text, toLang) => text,
25
+ /** 翻译器,决定自动翻译使用的api与调用方式,默认使用 Google 翻译器并使用7890(clash)端口代理 */
26
+ translator: new GoogleTranslator({
27
+ // proxyOption: {
28
+ // port: 7890,
29
+ // host: '127.0.0.1',
30
+ // headers: {
31
+ // 'User-Agent': 'Node'
32
+ // }
33
+ // }
34
+ })
35
+ };
36
+
37
+ module.exports = {
38
+ defaultOptions
39
+ };
@@ -0,0 +1,308 @@
1
+ const fs = require("fs-extra");
2
+ const {
3
+ isEmptyObject,
4
+ getLangJsonPath,
5
+ readJsonWithDefault,
6
+ sleep
7
+ } = require("./utils");
8
+ const { defaultOptions } = require("./options");
9
+
10
+ const SEPARATOR = "\n┇┇┇\n";
11
+ const SPLIT_SEPARATOR_REGEX = /\n┇ *┇ *┇\n/;
12
+
13
+ /**
14
+ * 智能文本分块处理器
15
+ * @param values 待分块的原始文本数组
16
+ * @param maxChunkSize 最大分块长度
17
+ * @returns 包含分块文本和重组方法的对象
18
+ *
19
+ * 功能特性:
20
+ * 1. 自动合并小文本为最大可能块
21
+ * 2. 处理超长文本并给出警告
22
+ * 3. 保证块长度不超过限制
23
+ * 4. 保留原始顺序和分隔符语义
24
+ */
25
+ function createTextSplitter(values, maxChunkSize) {
26
+ // 分隔符定义(用于合并/拆分时保持语义)
27
+ const SEP_LENGTH = SEPARATOR.length;
28
+
29
+ // 结果存储和缓冲区
30
+ const result = []; // 最终分块结果
31
+ let buffer = []; // 当前累积块缓冲区
32
+ let currentSize = 0; // 当前缓冲区字符数(含分隔符)
33
+
34
+ /**
35
+ * 提交缓冲区内容到结果集
36
+ * - 将缓冲区内容用分隔符连接
37
+ * - 重置缓冲区和计数器
38
+ */
39
+ const commitBuffer = () => {
40
+ if (buffer.length > 0) {
41
+ // 计算实际连接长度用于验证
42
+ const actualLength = buffer.join(SEPARATOR).length;
43
+ if (actualLength > maxChunkSize) {
44
+ console.warn(`缓冲区提交异常:生成块长度 ${actualLength} 超过限制`);
45
+ }
46
+
47
+ result.push(buffer.join(SEPARATOR));
48
+ buffer = [];
49
+ currentSize = 0;
50
+ }
51
+ };
52
+
53
+ // 主处理循环:遍历所有原始文本项
54
+ for (const value of values) {
55
+ // 计算需要新增的空间:文本长度 + 分隔符(非首项)
56
+ const neededSpace = value.length + (buffer.length > 0 ? SEP_LENGTH : 0);
57
+
58
+ // ─── 超长文本处理策略 ───
59
+ if (value.length > maxChunkSize) {
60
+ // 优先提交现有缓冲区内容
61
+ if (buffer.length > 0) commitBuffer();
62
+
63
+ /**
64
+ * 超长文本处理逻辑:
65
+ * - 长度超过1.5倍限制时发出强警告
66
+ * - 强制单独成块(即使超过限制)
67
+ * - 后续需要特殊处理这些异常块
68
+ */
69
+ if (value.length > maxChunkSize * 1.5) {
70
+ console.warn(
71
+ `超长文本告警:检测到长度 ${value.length} 字符的文本项,可能影响翻译质量`
72
+ );
73
+ }
74
+ // 结果直接新增一个超长文本
75
+ result.push(value);
76
+ continue;
77
+ }
78
+
79
+ // ─── 正常分块逻辑 ───
80
+ // 空间不足时提交当前缓冲区
81
+ if (currentSize + neededSpace > maxChunkSize) {
82
+ commitBuffer();
83
+ }
84
+
85
+ // 更新缓冲区状态(累加长度需包含分隔符)
86
+ currentSize += neededSpace;
87
+ buffer.push(value);
88
+ }
89
+
90
+ // 提交最终未完成的缓冲区内容
91
+ commitBuffer();
92
+
93
+ // 返回分块结果
94
+ return result;
95
+ }
96
+
97
+ function getI18nObjByLangKey(langKey, option) {
98
+ const langJsonPath = getLangJsonPath(langKey, option);
99
+ return readJsonWithDefault(langJsonPath, {});
100
+ }
101
+
102
+ function valuesToObject(values, keys) {
103
+ const obj = {};
104
+ for (let i = 0; i < keys.length; i++) {
105
+ obj[keys[i]] = values[i];
106
+ }
107
+ return obj;
108
+ }
109
+
110
+ function cleanI18nMap(sourceObj, targetObj) {
111
+ const result = {};
112
+ Object.keys(targetObj).forEach(key => {
113
+ if (sourceObj[key]) {
114
+ result[key] = targetObj[key];
115
+ }
116
+ });
117
+ return result;
118
+ }
119
+
120
+ async function autoTranslateFromFile() {
121
+ const fromLangMap = readJsonWithDefault(
122
+ getLangJsonPath(defaultOptions.fromLang, defaultOptions),
123
+ null
124
+ );
125
+ if (!fromLangMap) {
126
+ console.warn(
127
+ `❌ 源语言文件 ${defaultOptions.fromLang}.json 为空,请先添加源语言内容`
128
+ );
129
+ return;
130
+ }
131
+
132
+ autoTranslate(fromLangMap);
133
+ }
134
+
135
+ async function autoTranslate(i18nMap, option) {
136
+ option = { ...defaultOptions, ...option };
137
+ // 初始化现有翻译文件缓存
138
+ const langObjMap = {};
139
+ // 待翻译语言缓存
140
+ const toTransLangObjMap = {};
141
+
142
+ // 加载所有语言的现有翻译内容
143
+ option.translateLangKeys.forEach(langKey => {
144
+ langObjMap[langKey] = getI18nObjByLangKey(langKey, option);
145
+ toTransLangObjMap[langKey] = {};
146
+ });
147
+
148
+ // 比对出需要翻译的key
149
+ Object.keys(i18nMap).forEach(i18nKey => {
150
+ option.translateLangKeys.forEach(langKey => {
151
+ if (langObjMap[langKey] && !langObjMap[langKey][i18nKey]) {
152
+ toTransLangObjMap[langKey][i18nKey] = i18nMap[i18nKey];
153
+ }
154
+ });
155
+ });
156
+
157
+ // 遍历所有目标语言进行处理
158
+ for (const langKey of Object.keys(toTransLangObjMap)) {
159
+ const curLangObj = toTransLangObjMap[langKey];
160
+ const curLangKeys = Object.keys(curLangObj);
161
+
162
+ if (curLangKeys.length === 0) {
163
+ console.info(`${langKey}无需翻译,跳过...`);
164
+ continue;
165
+ }
166
+
167
+ console.info(
168
+ `[${option.translator.option.name}] 开始自动翻译${langKey}...`
169
+ );
170
+
171
+ // ─── 分块翻译流程开始 ───
172
+ const translatedValues = await translateChunks(curLangObj, langKey, option);
173
+ // ─── 分块翻译流程结束 ───=
174
+
175
+ // ─── 翻译结果校验 ───
176
+ if (translatedValues.length !== curLangKeys.length) {
177
+ console.error(
178
+ "❌ 使用付费翻译时,请检查翻译API额度是否充足,或是否已申请对应翻译API使用权限"
179
+ );
180
+ console.error(`❌ ${langKey}翻译结果不完整
181
+ 预期数量: ${curLangKeys.length}
182
+ 实际数量: ${translatedValues.length}
183
+ 样例数据: ${JSON.stringify(translatedValues.slice(0, 3))}`);
184
+ // return;
185
+ }
186
+
187
+ // 存储当前语言翻译结果
188
+ toTransLangObjMap[langKey] = valuesToObject(translatedValues, curLangKeys);
189
+ console.info(`✅ ${langKey} 翻译完成`);
190
+
191
+ // 设置翻译每种语言的间隔时间,防止被墙
192
+ await sleep(option.translateInterval);
193
+ }
194
+
195
+ // ─── 合并翻译结果到配置 ───
196
+ for (const langKey of Object.keys(toTransLangObjMap)) {
197
+ if (!isEmptyObject(toTransLangObjMap[langKey])) {
198
+ try {
199
+ const mergedLangObj = Object.assign(
200
+ langObjMap[langKey],
201
+ toTransLangObjMap[langKey]
202
+ );
203
+ // ─── 写入文件系统 ───
204
+ await fs.outputJson(getLangJsonPath(langKey, option), mergedLangObj, {
205
+ spaces: 2
206
+ });
207
+ console.info(`🎉 ${langKey} 配置文件已成功更新`);
208
+ } catch (error) {
209
+ console.error(`❌ ${langKey} 配置文件写入失败,原因:`, error);
210
+ }
211
+ }
212
+ }
213
+ }
214
+
215
+ // 清理无用的翻译(以源语言作对照)
216
+ async function cleanTranslate(option) {
217
+ option = { ...defaultOptions, ...option };
218
+ // 初始化现有翻译文件缓存
219
+ const langObjMap = {};
220
+ const fromLangI18nMap = getI18nObjByLangKey(option.fromLang, option);
221
+
222
+ // 加载所有语言的现有翻译内容
223
+ option.translateLangKeys.forEach(langKey => {
224
+ const currentLangI18nMap = getI18nObjByLangKey(langKey, option);
225
+ const cleanedI18nMap = cleanI18nMap(fromLangI18nMap, currentLangI18nMap);
226
+ if (
227
+ Object.keys(cleanedI18nMap).length !==
228
+ Object.keys(currentLangI18nMap).length
229
+ ) {
230
+ langObjMap[langKey] = cleanedI18nMap;
231
+ }
232
+ });
233
+
234
+ // 将清理后的语言对象写入文件
235
+ for (const langKey of Object.keys(langObjMap)) {
236
+ await fs.outputJson(getLangJsonPath(langKey, option), langObjMap[langKey], {
237
+ spaces: 2
238
+ });
239
+ console.info(`🎉 ${langKey} 配置文件清理成功`);
240
+ }
241
+ }
242
+
243
+ // 分块翻译流程函数
244
+ async function translateChunks(transLangObj, toTranslateLang, option) {
245
+ const { translator } = option;
246
+ // 获取分块后的文本列表
247
+ const translationChunks = createTextSplitter(
248
+ Object.values(transLangObj),
249
+ translator.option.maxChunkSize
250
+ );
251
+ // 并行执行分块翻译
252
+ const translatePromises = [];
253
+ for (let i = 0; i < translationChunks.length; i++) {
254
+ translatePromises.push(
255
+ translator.translate(
256
+ translationChunks[i],
257
+ option.fromLang,
258
+ toTranslateLang,
259
+ SEPARATOR
260
+ )
261
+ );
262
+ }
263
+
264
+ // 等待所有分块完成并合并结果
265
+ const chunkResults = await Promise.all(translatePromises);
266
+ const customTranslatedText =
267
+ typeof option.customTranslatedText !== "function"
268
+ ? text => text
269
+ : option.customTranslatedText;
270
+
271
+ return chunkResults
272
+ .map(item => {
273
+ // 提取分割逻辑到单独的函数中,提高代码复用性
274
+ const splitTranslation = (text, separatorRegex) => {
275
+ return text
276
+ .split(separatorRegex)
277
+ .map(v => customTranslatedText(v.trim(), toTranslateLang));
278
+ };
279
+
280
+ // 分割符可能会被翻译,所以这里做了兼容处理
281
+ if (SPLIT_SEPARATOR_REGEX.test(item)) {
282
+ return splitTranslation(item, SPLIT_SEPARATOR_REGEX);
283
+ } else {
284
+ const lines = item.split("\n");
285
+ const separator = lines.find(line => line.length === 3);
286
+ let value = [];
287
+ if (separator) {
288
+ value = splitTranslation(item, new RegExp(`\\n${separator}\\n`));
289
+ }
290
+ const realList = value.filter(Boolean);
291
+ if (realList.length > 1) {
292
+ return realList;
293
+ }
294
+ return splitTranslation(item, SPLIT_SEPARATOR_REGEX);
295
+ }
296
+ })
297
+ .flat();
298
+ }
299
+
300
+ module.exports = {
301
+ autoTranslateFromFile,
302
+ autoTranslate,
303
+ cleanTranslate,
304
+ translateChunks,
305
+ createTextSplitter,
306
+ getLangJsonPath,
307
+ cleanI18nMap
308
+ };
@@ -0,0 +1,91 @@
1
+ const axios = require("axios");
2
+ const { Translator } = require("./translator");
3
+ const CryptoJS = require("crypto-js");
4
+
5
+ /**
6
+ * 百度翻译器
7
+ *
8
+ * api文档:https://api.fanyi.baidu.com/product/113
9
+ *
10
+ * 使用方式:
11
+ * ```ts
12
+ * vitePluginI18n({
13
+ ...
14
+ translator: new BaiduTranslator({
15
+ appId: '你申请的appId',
16
+ appKey: '你申请的appKey'
17
+ })
18
+ })
19
+ * ```
20
+ */
21
+ class BaiduTranslator extends Translator {
22
+ /** 百度的语言类型映射不标准,需要手动控制 */
23
+ BAIDU_TRANSLATE_KEY_CONVERT_MAP = {
24
+ "zh-cn": "zh",
25
+ "zh-tw": "cht",
26
+ ja: "jp",
27
+ ko: "kor"
28
+ };
29
+
30
+ getTranslateKey(key) {
31
+ return this.BAIDU_TRANSLATE_KEY_CONVERT_MAP[key] || key;
32
+ }
33
+
34
+ constructor(option = {}) {
35
+ super({
36
+ name: "百度翻译",
37
+ fetchMethod: async (text, fromKey, toKey, separator) => {
38
+ let salt = new Date().getTime();
39
+
40
+ const data = {
41
+ q: text,
42
+ appid: option.appId,
43
+ from: this.getTranslateKey(fromKey),
44
+ to: this.getTranslateKey(toKey),
45
+ salt,
46
+ sign: CryptoJS.MD5(
47
+ option.appId + text + salt + option.appKey
48
+ ).toString()
49
+ };
50
+ const response = await axios.post(
51
+ "https://fanyi-api.baidu.com/api/trans/vip/translate",
52
+ data,
53
+ {
54
+ headers: {
55
+ "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
56
+ },
57
+ proxy: option.proxy
58
+ }
59
+ );
60
+
61
+ const { error_code, error_msg, trans_result } = response.data || {};
62
+
63
+ if (error_code) {
64
+ console.error(
65
+ `❌ 百度翻译错误:${error_code} - ${error_msg},请检查你的 appId 和 appKey 是否正确。`
66
+ );
67
+ return "";
68
+ }
69
+
70
+ const translatedTexts = trans_result
71
+ .map(item => item.dst)
72
+ .filter((_item, index) => index % 2 === 0)
73
+ .join(separator);
74
+
75
+ // 请求成功,返回响应数据
76
+ return translatedTexts || "";
77
+ },
78
+ onError: (error, cb) => {
79
+ cb(error);
80
+ console.error(
81
+ "请前往百度翻译官方申请翻译key,每个月都有免费额度,并请检查额度是否充足。"
82
+ );
83
+ },
84
+ interval: option.interval ?? 1000
85
+ });
86
+ }
87
+ }
88
+
89
+ module.exports = {
90
+ BaiduTranslator
91
+ };
@@ -0,0 +1,68 @@
1
+ const { translate } = require("@vitalets/google-translate-api");
2
+ const { Translator } = require("./translator");
3
+ const tunnel = require("tunnel");
4
+
5
+ /**
6
+ * 谷歌翻译器
7
+ *
8
+ * 基于@vitalets/google-translate-api,需要翻墙,不稳定,但是免费
9
+ *
10
+ * 使用方式:
11
+ * ```ts
12
+ * vitePluginI18n({
13
+ ...
14
+ translator: translator: new GoogleTranslator({
15
+ proxyOption: {
16
+ // 如果你本地的代理在127.0.0.0:8899
17
+ host: '127.0.0.1',
18
+ port: 8899,
19
+ headers: {
20
+ 'User-Agent': 'Node'
21
+ }
22
+ }
23
+ })
24
+ })
25
+ * ```
26
+ */
27
+ class GoogleTranslator extends Translator {
28
+ constructor(option = {}) {
29
+ super({
30
+ name: "Google翻译",
31
+ fetchMethod: async (text, fromKey, toKey, separator) => {
32
+ let data = await translate(text, {
33
+ from: fromKey,
34
+ to: toKey,
35
+ ...(option.proxyOption
36
+ ? {
37
+ fetchOptions: {
38
+ agent: tunnel.httpsOverHttp({
39
+ proxy: option.proxyOption
40
+ })
41
+ }
42
+ }
43
+ : {})
44
+ });
45
+ const result = data["text"] || "";
46
+ if (separator && toKey === "th") {
47
+ return result.replace(/\n\n/g, separator);
48
+ }
49
+ return result;
50
+ },
51
+ onError: (error, cb) => {
52
+ cb(error);
53
+ if (
54
+ error instanceof Object &&
55
+ "code" in error &&
56
+ error.code === "ETIMEDOUT"
57
+ ) {
58
+ console.error("❗ 请求超时,请确保你的网络可以访问google ❗");
59
+ }
60
+ },
61
+ interval: option.interval ?? 1000
62
+ });
63
+ }
64
+ }
65
+
66
+ module.exports = {
67
+ GoogleTranslator
68
+ };
@@ -0,0 +1,15 @@
1
+ const { Translator } = require("./translator");
2
+ const { GoogleTranslator } = require("./google");
3
+ const { YoudaoTranslator } = require("./youdao");
4
+ const { BaiduTranslator } = require("./baidu");
5
+ const { EmptyTranslator } = require("./scan");
6
+ const { VolcEngineTranslator } = require("./volcengine");
7
+
8
+ module.exports = {
9
+ Translator,
10
+ GoogleTranslator,
11
+ YoudaoTranslator,
12
+ BaiduTranslator,
13
+ EmptyTranslator,
14
+ VolcEngineTranslator
15
+ };
@@ -0,0 +1,24 @@
1
+ const { Translator } = require("./translator");
2
+
3
+ /**
4
+ * 空翻译器,不翻译文本,用于配合某些特殊的操作
5
+ */
6
+ class EmptyTranslator extends Translator {
7
+ constructor(option = {}) {
8
+ const resultOption = {
9
+ name: "空翻译器",
10
+ fetchMethod: async (text, _from, _to, separator) => {
11
+ // 相当于把翻译结果统一设置为空串
12
+ const value = text.split(separator).fill("");
13
+ return value.join(separator);
14
+ },
15
+ ...option
16
+ };
17
+ super(resultOption);
18
+ }
19
+ // TODO: 后续可以作为基类,提供更多的配置选项
20
+ }
21
+
22
+ module.exports = {
23
+ EmptyTranslator
24
+ };
@@ -0,0 +1,60 @@
1
+ /**
2
+ * 间隔执行队列
3
+ */
4
+ class IntervalQueue {
5
+ queue = [];
6
+ isRunning = false;
7
+
8
+ /**
9
+ * @param fn 执行函数
10
+ * @param delay 执行间隔
11
+ * @param timeout 超时时间
12
+ */
13
+ constructor(fn, delay, timeout) {
14
+ this.fn = fn;
15
+ this.delay = delay;
16
+ this.timeout = timeout;
17
+ }
18
+
19
+ async wait(delay = this.delay) {
20
+ await new Promise(resolve => setTimeout(resolve, delay));
21
+ }
22
+
23
+ async run() {
24
+ if (this.isRunning) return;
25
+ let item;
26
+ while ((item = this.queue.shift())) {
27
+ const { args, resolve, reject } = item;
28
+ this.isRunning = true;
29
+ try {
30
+ const result = await this.fn(...args);
31
+ resolve(result);
32
+ } catch (e) {
33
+ reject(e);
34
+ }
35
+ await this.wait();
36
+ }
37
+ this.isRunning = false;
38
+ }
39
+
40
+ /**
41
+ * 执行一次fn
42
+ * @param args fn的入参
43
+ * @returns 返回fn的返回值的Promise
44
+ */
45
+ execute(...args) {
46
+ return new Promise((resolve, reject) => {
47
+ this.queue.push({ args, resolve, reject });
48
+ this.run();
49
+ if (this.timeout) {
50
+ setTimeout(() => {
51
+ reject(new Error("IntervalQueue timeout"));
52
+ }, this.timeout);
53
+ }
54
+ });
55
+ }
56
+ }
57
+
58
+ module.exports = {
59
+ IntervalQueue
60
+ };