xshell 1.3.3 → 1.3.4

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/file.d.ts CHANGED
@@ -115,7 +115,7 @@ export interface FListOptions {
115
115
  stats?: boolean;
116
116
  best_effort?: boolean;
117
117
  }
118
- /**
118
+ /** 列出文件夹,默认返回文件名列表,也可以传入 stats 选项返回元数据
119
119
  - fpd: 文件夹完整路径 absolute path of directory
120
120
  - options?:
121
121
  - deep?: `false` 递归遍历 recursively
package/git.js CHANGED
@@ -98,7 +98,7 @@ export class Git {
98
98
  '--prune'
99
99
  ], print_stdout);
100
100
  if (print)
101
- console.log('更新远程分支成功');
101
+ console.log(`${this.cwd.fname.slice(0, -1)} 更新远程分支成功`);
102
102
  }
103
103
  async checkout(branch = 'main') {
104
104
  let create = false;
package/net.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type net from 'node:net';
1
2
  import { type Readable } from 'node:stream';
2
3
  import type { Cookie, CookieJar, MemoryCookieStore } from 'tough-cookie';
3
4
  import type { Encoding } from './file.ts';
@@ -48,7 +49,6 @@ export interface RequestOptions {
48
49
  retry: boolean;
49
50
  timeout: boolean;
50
51
  };
51
- long_connection?: true;
52
52
  }
53
53
  export interface RequestFullOptions extends RequestOptions {
54
54
  full: true;
@@ -97,8 +97,7 @@ export declare class StatusCodeError extends Error {
97
97
  - decode?: `true` 根据 content-encoding: br 等解码压缩后的 response.body
98
98
  - print?:
99
99
  - retry: `true` 是否打印 "等待 1 秒后重试 request" 提示信息
100
- - timeout: `true` 是否打印最后一次超时重试失败时的错误
101
- - long_connection?: 用于保持长 tcp 连接,保留 nat 端口映射记录 */
100
+ - timeout: `true` 是否打印最后一次超时重试失败时的错误 */
102
101
  export declare function request(url: string | URL): Promise<string>;
103
102
  export declare function request(url: string | URL, options: RequestRawOptions): Promise<RawResponse>;
104
103
  export declare function request(url: string | URL, options: RequestFullOptions & {
@@ -111,3 +110,4 @@ export declare function request(url: string | URL, options: RequestOptions & {
111
110
  export declare function request(url: string | URL, options: RequestOptions): Promise<string>;
112
111
  /** 发起 http 请求并将响应体作为 json 解析 */
113
112
  export declare function request_json<T = any>(url: string | URL, options?: RequestOptions): Promise<T>;
113
+ export declare function get_socket_ip(socket: net.Socket): string;
package/net.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import zlib from 'node:zlib';
2
2
  import { buffer as stream_to_buffer, text as stream_to_text } from 'node:stream/consumers';
3
- import { isReadable } from 'node:stream';
4
- import { inspect, assert, delay, pipe_with_error, map_values, unique, timeout, check, colored, encode } from "./utils.js";
3
+ import { pipeline, isReadable } from 'node:stream';
4
+ import { noop } from "./prototype.js";
5
+ import { inspect, assert, delay, map_values, unique, timeout, check, colored, encode, TimeoutError } from "./utils.js";
5
6
  import { drop_request_headers } from "./net.common.js";
6
7
  export * from "./net.common.js";
7
8
  export var MyProxy;
@@ -32,27 +33,25 @@ async function request_retry(url, options, _timeout, retries = 0, count = 0, pri
32
33
  // 设置给 undici 设置 timeout, signal 不一定管用,还是得自己兜底
33
34
  options.signal = AbortSignal.timeout(_timeout);
34
35
  return await timeout(_timeout + 300, // 为 undici 兜底
35
- undici.request(url, options), undefined, print.timeout && count >= retries // 只打印最后一次超时的错误,避免太多冗余输出
36
- );
36
+ undici.request(url, options), undefined, print.timeout && count >= retries); // 只打印最后一次超时的错误,避免太多冗余输出
37
37
  }
38
38
  else
39
39
  return await undici.request(url, options);
40
40
  }
41
41
  catch (error) {
42
- if (error.name === 'TimeoutError')
43
- if (count < retries) {
44
- const duration = 2 ** count;
45
- if (print.retry)
46
- console.log(`${`等待 ${duration} 秒后重试请求 (已尝试 ${count + 1} 次) ··`.yellow} ${url.toString().blue.underline}`);
47
- await delay(1000 * duration);
48
- return request_retry(url, options, _timeout, retries, count + 1, print);
49
- }
50
- else {
51
- const seconds = _timeout / 1000;
52
- throw Object.assign(new Error(`请求超过 ${seconds.toFixed(seconds < 1 ? 1 : 0)} 秒等待时间: ${url.toString()}`), { name: 'TimeoutError' });
53
- }
54
- else
42
+ if (error.name !== 'TimeoutError')
55
43
  throw error;
44
+ // 重试次数内
45
+ if (count < retries) {
46
+ const duration = 2 ** count;
47
+ if (print.retry)
48
+ console.log(`等待 ${duration} 秒后重试请求 (已尝试 ${count + 1} 次) ··`.yellow + ' ' +
49
+ url.toString().blue.underline);
50
+ await delay(1000 * duration);
51
+ return request_retry(url, options, _timeout, retries, count + 1, print);
52
+ }
53
+ const seconds = _timeout / 1000;
54
+ throw new TimeoutError(`请求超过 ${seconds.toFixed(seconds < 1 ? 1 : 0)} 秒等待时间: ${url.toString()}`);
56
55
  }
57
56
  }
58
57
  export class StatusCodeError extends Error {
@@ -71,7 +70,7 @@ export async function request(url, options = {}) {
71
70
  const { queries, headers: _headers, body, type = 'application/json', timeout = 5 * 1000, auth, cookies: _cookies, raw = false, full = false, redirect = 'follow', decode = true, print = {
72
71
  timeout: true,
73
72
  retry: true
74
- }, long_connection } = options;
73
+ } } = options;
75
74
  let { method, retries, encoding, proxy, } = options;
76
75
  url = new URL(url);
77
76
  if (queries)
@@ -128,7 +127,7 @@ export async function request(url, options = {}) {
128
127
  proxy = MyProxy.socks5;
129
128
  let undici_options = {
130
129
  ...method ? { method } : {},
131
- dispatcher: get_dispatcher(proxy, redirect, long_connection),
130
+ dispatcher: get_dispatcher(proxy, redirect),
132
131
  // 下面这些 timeout 都不是总的时间
133
132
  headersTimeout: timeout,
134
133
  // 从收完 headers 开始算
@@ -155,30 +154,26 @@ export async function request(url, options = {}) {
155
154
  switch (content_encoding) {
156
155
  case 'gzip':
157
156
  case 'x-gzip':
158
- body = pipe_with_error(_body, zlib.createGunzip({
157
+ body = pipeline(_body, zlib.createGunzip({
159
158
  // Be less strict when decoding compressed responses, since sometimes
160
159
  // servers send slightly invalid responses that are still accepted
161
160
  // by common browsers.
162
161
  // Always using Z_SYNC_FLUSH is what cURL does.
163
162
  flush: zlib.constants.Z_SYNC_FLUSH,
164
163
  finishFlush: zlib.constants.Z_SYNC_FLUSH
165
- }));
164
+ }), noop);
166
165
  break;
167
166
  case 'deflate':
168
- body = pipe_with_error(_body, zlib.createInflate());
167
+ body = pipeline(_body, zlib.createInflate(), noop);
169
168
  break;
170
169
  case 'br':
171
- body = pipe_with_error(_body, zlib.createBrotliDecompress());
170
+ body = pipeline(_body, zlib.createBrotliDecompress(), noop);
172
171
  break;
173
172
  default:
174
173
  throw new Error(`不支持 content-encoding: ${content_encoding.quote()} 的 http 请求`);
175
174
  }
176
175
  }
177
- response = {
178
- status,
179
- headers,
180
- body
181
- };
176
+ response = { status, headers, body };
182
177
  if (!((200 <= status && status <= 299) || status === 304 || (redirect === 'manual' && 300 <= status && status < 400)))
183
178
  throw new StatusCodeError(status, url.toString());
184
179
  }
@@ -234,8 +229,8 @@ export async function request(url, options = {}) {
234
229
  :
235
230
  body_;
236
231
  }
237
- function get_dispatcher(proxy, redirect, long_connection) {
238
- const key = `${proxy || (long_connection ? 'direct.long' : 'direct')}.${redirect}`;
232
+ function get_dispatcher(proxy, redirect) {
233
+ const key = `${proxy || 'direct'}.${redirect}`;
239
234
  let dispatcher = dispatchers[key];
240
235
  if (dispatcher)
241
236
  return dispatcher;
@@ -243,7 +238,7 @@ function get_dispatcher(proxy, redirect, long_connection) {
243
238
  // @ts-ignore
244
239
  new undici.ProxyAgent({ uri: proxy })
245
240
  :
246
- new undici.Agent({ keepAliveTimeout: long_connection ? 30_000 : undefined });
241
+ new undici.Agent({ allowH2: true });
247
242
  if (redirect === 'follow')
248
243
  dispatcher = dispatcher.compose(
249
244
  // todo: 强制手动处理重定向,来正确处理 cookie ?
@@ -297,4 +292,7 @@ export async function request_json(url, options) {
297
292
  throw error;
298
293
  }
299
294
  }
295
+ export function get_socket_ip(socket) {
296
+ return socket.remoteAddress.strip_if_start('::ffff:');
297
+ }
300
298
  //# sourceMappingURL=net.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xshell",
3
- "version": "1.3.3",
3
+ "version": "1.3.4",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "bin": {
@@ -56,19 +56,19 @@
56
56
  "@stylistic/eslint-plugin": "^5.5.0",
57
57
  "@svgr/webpack": "^8.1.0",
58
58
  "@types/sass-loader": "^8.0.10",
59
- "@typescript-eslint/eslint-plugin": "^8.46.2",
60
- "@typescript-eslint/parser": "^8.46.2",
61
- "@typescript-eslint/utils": "^8.46.2",
59
+ "@typescript-eslint/eslint-plugin": "^8.46.3",
60
+ "@typescript-eslint/parser": "^8.46.3",
61
+ "@typescript-eslint/utils": "^8.46.3",
62
62
  "archiver": "^7.0.1",
63
63
  "chalk": "^5.6.2",
64
64
  "commander": "^14.0.2",
65
65
  "css-loader": "^7.1.2",
66
66
  "emoji-regex": "^10.6.0",
67
- "eslint": "^9.39.0",
67
+ "eslint": "^9.39.1",
68
68
  "eslint-plugin-import": "^2.32.0",
69
69
  "eslint-plugin-react": "^7.37.5",
70
70
  "https-proxy-agent": "^7.0.6",
71
- "i18next": "^25.6.0",
71
+ "i18next": "^25.6.1",
72
72
  "i18next-scanner": "^4.6.0",
73
73
  "koa": "^3.1.1",
74
74
  "koa-compress": "^5.1.1",
@@ -76,7 +76,7 @@
76
76
  "mime-types": "^3.0.1",
77
77
  "p-map": "^7.0.3",
78
78
  "react": "^19.2.0",
79
- "react-i18next": "^16.2.3",
79
+ "react-i18next": "^16.2.4",
80
80
  "resolve-path": "^1.4.0",
81
81
  "sass": "^1.93.3",
82
82
  "sass-loader": "^16.0.6",
@@ -101,7 +101,7 @@
101
101
  "@types/koa": "^3.0.1",
102
102
  "@types/koa-compress": "^4.0.7",
103
103
  "@types/mime-types": "^3.0.1",
104
- "@types/node": "^24.9.2",
104
+ "@types/node": "^24.10.0",
105
105
  "@types/react": "^19.2.2",
106
106
  "@types/tough-cookie": "^4.0.5",
107
107
  "@types/vscode": "^1.105.0",
package/server.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createServer as http_create_server, } from 'node:http';
2
- import { createSecureServer as http2_create_server } from 'node:http2';
1
+ import { default as http, } from 'node:http';
2
+ import { default as http2 } from 'node:http2';
3
3
  import { createSecureContext } from 'node:tls';
4
4
  import zlib from 'node:zlib';
5
5
  import fs from 'node:fs';
@@ -12,8 +12,8 @@ import resolve_safely from 'resolve-path';
12
12
  import { contentType as get_content_type } from 'mime-types';
13
13
  // --- my libs
14
14
  import { t } from "./i18n/instance.js";
15
- import { request as _request, Remote } from "./net.js";
16
- import { inspect, check, range_to_numbers, encode, filter_keys, filter_values, consume_stream, colored, url_width } from "./utils.js";
15
+ import { request as _request, Remote, get_socket_ip } from "./net.js";
16
+ import { inspect, check, range_to_numbers, encode, filter_keys, filter_values, consume_stream, colored, url_width, defer2 } from "./utils.js";
17
17
  import { flist, fread, fstat } from "./file.js";
18
18
  import { exe_nodejs, sea } from "./process.js";
19
19
  // ------------ my server
@@ -138,9 +138,8 @@ export class Server {
138
138
  app.use(this._router.bind(this));
139
139
  this.app = app;
140
140
  this.handler = this.app.callback();
141
- this.http_server = http_create_server({ optimizeEmptyRequests: true }, this.handler);
142
- const { http, http2 } = this;
143
- if (http2) {
141
+ this.http_server = http.createServer({ optimizeEmptyRequests: true, noDelay: true }, this.handler);
142
+ if (this.http2) {
144
143
  const { fpd_certs } = this;
145
144
  let lazy_secure_ctxs = Object.fromEntries(await Promise.all(
146
145
  // fpd_certs 文件夹下面的每个 .key 对应一个证书及域名
@@ -157,7 +156,7 @@ export class Server {
157
156
  })));
158
157
  const default_ctx = lazy_secure_ctxs[this.default_hostnames.find(hostname => hostname in lazy_secure_ctxs)];
159
158
  check(default_ctx);
160
- this.http2_server = http2_create_server({
159
+ this.http2_server = http2.createSecureServer({
161
160
  SNICallback(servername, callback) {
162
161
  let lazy_ctx = lazy_secure_ctxs[servername] ||
163
162
  lazy_secure_ctxs[servername.replace(/^.*?\./, '*.')] ||
@@ -166,6 +165,7 @@ export class Server {
166
165
  callback(null, lazy_ctx.ctx ??= createSecureContext(lazy_ctx));
167
166
  },
168
167
  allowHTTP1: true,
168
+ noDelay: true
169
169
  }, this.handler);
170
170
  }
171
171
  // websocket rpc
@@ -229,20 +229,16 @@ export class Server {
229
229
  }
230
230
  await this.init_server();
231
231
  await Promise.all([
232
- http && new Promise(resolve => {
233
- this.http_server.listen(this.http_port, resolve);
234
- }),
235
- http2 && new Promise(resolve => {
236
- this.http2_server.listen(this.http2_port, resolve);
237
- }),
232
+ this.http && listen_http_server(this.http_server, this.http_port),
233
+ this.http2 && listen_http_server(this.http2_server, this.http2_port)
238
234
  ]);
239
235
  if (this.print.info)
240
236
  console.log(t('{{name}} 启动成功,正在监听 {{ports}} 端口', {
241
237
  name: this.name,
242
238
  ports: [
243
- ...http ? [this.http_port] : [],
244
- ...http2 ? [this.http2_port] : []
245
- ].join(', ')
239
+ this.http && this.http_port,
240
+ this.http2 && this.http2_port
241
+ ].filter(Boolean).join(', ')
246
242
  }));
247
243
  }
248
244
  try_write_stdio_subscribers(chunk) {
@@ -258,25 +254,25 @@ export class Server {
258
254
  }
259
255
  stop() {
260
256
  this.http_server.close();
261
- if (this.http2_server)
262
- this.http2_server.close();
257
+ this.http2_server?.close();
263
258
  }
264
259
  /** 可被子类重写定义错误处理逻辑 */
265
260
  on_error(error, ctx) {
266
- if (this.print.errors) {
267
- const code = error?.code;
268
- if (code === 'EPIPE' || code === 'ECONNRESET')
269
- console.log(`${error.code}:`, ctx?.request?.url);
270
- else {
271
- console.error(error);
272
- if (ctx)
273
- console.log('ctx:', ctx);
274
- }
261
+ if (!this.print.errors)
262
+ return;
263
+ const code = error?.code;
264
+ if (code === 'EPIPE' || code === 'ECONNRESET')
265
+ console.log(`${error.code}:`, ctx?.request?.url);
266
+ else {
267
+ console.error(error);
268
+ if (ctx)
269
+ console.log('ctx:', ctx);
275
270
  }
276
271
  }
277
272
  on_upgrade(request, socket, head) {
278
273
  // url 只有路径部分
279
274
  const { url, headers, headers: { host = '' }, } = request;
275
+ const ip = get_socket_ip(request.socket);
280
276
  if (this.print.logs) {
281
277
  let s =
282
278
  // 时间
@@ -289,7 +285,7 @@ export class Server {
289
285
  url;
290
286
  s = s.pad(url_width);
291
287
  // ip
292
- s += ` <- ${request.socket.remoteAddress.strip_if_start('::ffff:')}`;
288
+ s += ` <- ${ip}`;
293
289
  // 客户端信息
294
290
  const client_info = this.get_client_info(headers);
295
291
  if (client_info)
@@ -327,7 +323,7 @@ export class Server {
327
323
  console.error(error);
328
324
  response.status = error.status || 500;
329
325
  response.set('content-type', text_plain);
330
- response.body = inspect(error, { colors: false });
326
+ response.body = inspect_error(error);
331
327
  }
332
328
  }
333
329
  /** 解析 req.body to request.body
@@ -638,7 +634,8 @@ export class Server {
638
634
  if (this.print.errors)
639
635
  console.log(error);
640
636
  response.status = 500;
641
- response.body = inspect(error, { colors: false });
637
+ response.set('content-type', text_plain);
638
+ response.body = inspect_error(error);
642
639
  }
643
640
  }
644
641
  return true;
@@ -800,7 +797,7 @@ export class Server {
800
797
  - reverse?: `false` 在 range 内从后往前尝试 */
801
798
  static async get_available_port(range, reverse = false) {
802
799
  for (const port of range_to_numbers(range, reverse)) {
803
- let server = http_create_server();
800
+ let server = http.createServer();
804
801
  try {
805
802
  await new Promise((resolve, reject) => {
806
803
  server.once('error', reject);
@@ -825,4 +822,17 @@ export class Server {
825
822
  }
826
823
  export const text_plain = 'text/plain; charset=utf-8';
827
824
  const devtools_trash = '/.well-known/appspecific/com.chrome.devtools.json';
825
+ function listen_http_server(server, port) {
826
+ let plisten = defer2();
827
+ server.once('error', plisten.reject);
828
+ server.listen(port, () => {
829
+ server.off('error', plisten.reject);
830
+ plisten.resolve();
831
+ });
832
+ return plisten;
833
+ }
834
+ const no_color = { colors: false };
835
+ function inspect_error(error) {
836
+ return inspect(error, no_color);
837
+ }
828
838
  //# sourceMappingURL=server.js.map
package/utils.common.d.ts CHANGED
@@ -50,7 +50,7 @@ export declare function filter_values<TObj extends Record<string, any>>(obj: TOb
50
50
  /** 简单选择对象中的部分 keys, 返回新对象 */
51
51
  export declare function pick<TObject = any>(obj: TObject, keys: (keyof TObject)[]): Partial<TObject>;
52
52
  /** 忽略对象中的 keys, 返回新对象 */
53
- export declare function omit<TObj>(obj: TObj, omit_keys: string[]): TObj;
53
+ export declare function omit<TObj>(obj: TObj, omit_keys: string[] | Set<string>): TObj;
54
54
  /** 拼接 TypedArrays 生成一个完整的 Uint8Array */
55
55
  export declare function concat(arrays: ArrayBufferView[]): Uint8Array<ArrayBufferLike>;
56
56
  export declare let encoder: TextEncoder;
@@ -67,8 +67,9 @@ export declare function buffer_equals(left: Uint8Array, right: Uint8Array | stri
67
67
  - milliseconds: 限时毫秒数
68
68
  - action?: 要等待运行的任务, async function 或 promise
69
69
  - on_timeout?: 超时后调用的函数
70
- - 若传: 调用 on_timeout,参数为 TimeoutError,然后 timeout 函数正常返回 null
71
- - 若不传: 抛出 TimeoutError
70
+ - 若传: 超时时调用会调用 on_timeout,参数为 TimeoutError,然后等待 on_timeout 执行完成
71
+ 最后 timeout 函数正常返回 null,如果 on_timeout 报错,同样会 reject
72
+ - 若不传: 直接抛出 TimeoutError
72
73
  - print?: `true` 打印已超时任务的错误 */
73
74
  export declare function timeout<TReturn>(milliseconds: number, action: Promise<TReturn> | (() => Promise<TReturn>), on_timeout?: (error: TimeoutError) => void | Promise<void>, print?: boolean): Promise<TReturn>;
74
75
  /** 轮询尝试 action 共 times 次,每次间隔 duration
@@ -118,7 +119,7 @@ export type StrictArrayBuffer = ArrayBuffer & {
118
119
  };
119
120
  export interface Deferred<TValue> extends Promise<TValue> {
120
121
  resolve(value: TValue | PromiseLike<TValue>): void;
121
- reject(reason?: Error): void;
122
+ reject(error?: Error): void;
122
123
  }
123
124
  /** 创建一个 promise,后续可调用 promise.resolve, promise.reject 方法设置其状态和值
124
125
  - initial?: `undefined` 传入非 undefined 值(包括 null)时直接设置为 resolved 状态
@@ -126,7 +127,7 @@ export interface Deferred<TValue> extends Promise<TValue> {
126
127
  export declare function defer<TValue>(initial?: TValue): Deferred<TValue>;
127
128
  export interface Deferred2<TValue> extends Promise<TValue> {
128
129
  resolve(value: TValue | PromiseLike<TValue>): void;
129
- reject(reason?: Error): void;
130
+ reject(error?: Error): void;
130
131
  get settled(): boolean;
131
132
  }
132
133
  /** 有 settled 状态的 defer */
@@ -167,7 +168,7 @@ export declare function lowercase_first_letter(str: string): string;
167
168
  export declare function ceil2(n: number): number;
168
169
  /** 节流,最多只在时间间隔末尾调用一次,可以设置首次调用是否延后 */
169
170
  export declare function throttle(duration: number, func: Function, delay_first?: boolean): (this: any, ...args: any[]) => void;
170
- /** 防抖,间隔一段时间不再触发时调用 */
171
+ /** 防抖,间隔一段时间不再触发时调用,同时大幅优化减少 setTimeout, clearTimeout 的调用次数 */
171
172
  export declare function debounce(duration: number, func: Function): (this: any, ...args: any[]) => void;
172
173
  export declare function tomorrow(date?: Date | string | number): Date;
173
174
  export declare function to_csv_field(str: string): string;
@@ -176,10 +177,3 @@ export declare function set_error_message(error: Error, message: string): Error;
176
177
  /** 比较两个数组中的元素完全相同,数组元素用引用比较 */
177
178
  export declare function array_equals(a: any[], b: any[]): boolean;
178
179
  export declare function nowstr(with_date?: boolean): string;
179
- interface MyTimeout {
180
- clear(): void;
181
- ref: NodeJS.Timeout | number;
182
- }
183
- /** 面向对象的 timeout, callback 在后的 timeout */
184
- export declare function set_timeout(delay: number, callback: VoidFunction): MyTimeout;
185
- export {};
package/utils.common.js CHANGED
@@ -136,8 +136,11 @@ export function pick(obj, keys) {
136
136
  }
137
137
  /** 忽略对象中的 keys, 返回新对象 */
138
138
  export function omit(obj, omit_keys) {
139
- const omit_keys_ = new Set(omit_keys);
140
- return filter_keys(obj, key => !omit_keys_.has(key));
139
+ const set = omit_keys instanceof Set;
140
+ return filter_keys(obj, set ?
141
+ key => !(omit_keys.has(key))
142
+ :
143
+ key => !(omit_keys.includes(key)));
141
144
  }
142
145
  /** 拼接 TypedArrays 生成一个完整的 Uint8Array */
143
146
  export function concat(arrays) {
@@ -184,54 +187,49 @@ export function buffer_equals(left, right) {
184
187
  - milliseconds: 限时毫秒数
185
188
  - action?: 要等待运行的任务, async function 或 promise
186
189
  - on_timeout?: 超时后调用的函数
187
- - 若传: 调用 on_timeout,参数为 TimeoutError,然后 timeout 函数正常返回 null
188
- - 若不传: 抛出 TimeoutError
190
+ - 若传: 超时时调用会调用 on_timeout,参数为 TimeoutError,然后等待 on_timeout 执行完成
191
+ 最后 timeout 函数正常返回 null,如果 on_timeout 报错,同样会 reject
192
+ - 若不传: 直接抛出 TimeoutError
189
193
  - print?: `true` 打印已超时任务的错误 */
190
194
  export async function timeout(milliseconds, action, on_timeout, print = true) {
191
195
  const error = new TimeoutError();
192
- return new Promise((resolve, reject) => {
193
- let done = false;
194
- let rejected = false;
195
- (async () => {
196
- await delay(milliseconds);
197
- if (!done)
198
- if (on_timeout)
199
- try {
200
- await on_timeout(error);
201
- resolve(null);
202
- }
203
- catch (error) {
204
- if (rejected)
205
- throw error; // 会成为 unhandled rejection
206
- else {
207
- rejected = true;
208
- reject(error);
209
- }
210
- }
211
- else {
212
- rejected = true;
213
- reject(error);
214
- }
215
- })();
216
- (async () => {
196
+ let presult = defer2();
197
+ let waiting_on_timeout = false;
198
+ let timeout = setTimeout(async () => {
199
+ if (on_timeout) {
217
200
  try {
218
- resolve(await (typeof action === 'function' ? action() : action));
201
+ waiting_on_timeout = true;
202
+ await on_timeout(error);
203
+ presult.resolve(null);
219
204
  }
220
205
  catch (error) {
221
- if (rejected) {
222
- if (print)
223
- console.log(`已超时任务的错误: ${error.message}`);
224
- }
225
- else {
226
- rejected = true;
227
- reject(error);
228
- }
229
- }
230
- finally {
231
- done = true;
206
+ presult.reject(error);
232
207
  }
233
- })();
208
+ }
209
+ else
210
+ presult.reject(error);
211
+ }, milliseconds);
212
+ const paction = typeof action === 'function' ? action() : action;
213
+ paction.then(result => {
214
+ if (presult.settled) {
215
+ if (print)
216
+ console.log('已超时任务最终完成了');
217
+ }
218
+ else if (!waiting_on_timeout) {
219
+ presult.resolve(result);
220
+ clearTimeout(timeout);
221
+ }
222
+ }, error => {
223
+ if (presult.settled) {
224
+ if (print)
225
+ console.error(`已超时任务的错误: ${error.message}`);
226
+ }
227
+ else if (!waiting_on_timeout) {
228
+ presult.reject(error);
229
+ clearTimeout(timeout);
230
+ }
234
231
  });
232
+ return presult;
235
233
  }
236
234
  /** 轮询尝试 action 共 times 次,每次间隔 duration
237
235
  action 返回 trusy 值时认为成功,返回 action 的结果
@@ -382,19 +380,18 @@ export class TimeoutError extends Error {
382
380
  // eslint-disable-next-line @typescript-eslint/promise-function-async
383
381
  export function defer(initial) {
384
382
  if (initial === undefined) {
385
- let resolve;
386
- let reject;
387
- let promise = new Promise((_resolve, _reject) => {
388
- resolve = _resolve;
389
- reject = _reject;
390
- });
391
- return Object.assign(promise, { resolve, reject });
383
+ let { promise, resolve, reject } = Promise.withResolvers();
384
+ let p = promise;
385
+ p.resolve = resolve;
386
+ p.reject = reject;
387
+ return p;
388
+ }
389
+ else {
390
+ let p = Promise.resolve(initial);
391
+ p.resolve = noop;
392
+ p.reject = noop;
393
+ return p;
392
394
  }
393
- else
394
- return Object.assign(Promise.resolve(initial), {
395
- resolve: noop,
396
- reject: noop
397
- });
398
395
  }
399
396
  /** 有 settled 状态的 defer */
400
397
  // eslint-disable-next-line @typescript-eslint/promise-function-async
@@ -402,26 +399,29 @@ export function defer2(initial) {
402
399
  if (initial === undefined) {
403
400
  let settled = false;
404
401
  let { promise, resolve, reject } = Promise.withResolvers();
405
- return Object.assign(promise, {
406
- resolve(value) {
407
- settled = true;
408
- resolve(value);
409
- },
410
- reject(error) {
411
- settled = true;
412
- reject(error);
413
- },
414
- get settled() {
415
- return settled;
416
- }
402
+ let p = promise;
403
+ p.resolve = (value) => {
404
+ settled = true;
405
+ resolve(value);
406
+ };
407
+ p.reject = (error) => {
408
+ settled = true;
409
+ reject(error);
410
+ };
411
+ Object.defineProperty(p, 'settled', {
412
+ get() { return settled; },
413
+ enumerable: true,
414
+ configurable: true
417
415
  });
416
+ return p;
417
+ }
418
+ else {
419
+ let p = Promise.resolve(initial);
420
+ p.resolve = noop;
421
+ p.reject = noop;
422
+ p.settled = true;
423
+ return p;
418
424
  }
419
- else
420
- return Object.assign(Promise.resolve(initial), {
421
- resolve: noop,
422
- reject: noop,
423
- settled: true
424
- });
425
425
  }
426
426
  /** @example
427
427
  let lock = new Lock(redis)
@@ -508,8 +508,8 @@ export function ceil2(n) {
508
508
  export function throttle(duration, func, delay_first = false) {
509
509
  let timeout = 0;
510
510
  let last = 0;
511
- let saved_this = null;
512
- let saved_args = null;
511
+ let saved_this;
512
+ let saved_args;
513
513
  return function throttled(...args) {
514
514
  // 当前时间间隔已预定执行,本次调用仅更新调用参数
515
515
  if (timeout) {
@@ -539,14 +539,33 @@ export function throttle(duration, func, delay_first = false) {
539
539
  }
540
540
  };
541
541
  }
542
- /** 防抖,间隔一段时间不再触发时调用 */
542
+ /** 防抖,间隔一段时间不再触发时调用,同时大幅优化减少 setTimeout, clearTimeout 的调用次数 */
543
543
  export function debounce(duration, func) {
544
- let timeout = null;
544
+ const half = Math.floor(duration / 2);
545
+ let timeout;
546
+ let last = 0;
547
+ let saved_args;
548
+ let saved_this;
549
+ function debounce_callback() {
550
+ timeout = null;
551
+ func.apply(saved_this, saved_args);
552
+ saved_this = null;
553
+ saved_args = null;
554
+ }
545
555
  return function debounced(...args) {
556
+ if (!timeout) {
557
+ timeout = setTimeout(debounce_callback, duration);
558
+ last = Date.now();
559
+ return;
560
+ }
561
+ const now = Date.now();
562
+ saved_args = args;
563
+ saved_this = this;
564
+ if (now - last < half)
565
+ return;
546
566
  clearTimeout(timeout);
547
- timeout = setTimeout(() => {
548
- func.apply(this, args);
549
- }, duration);
567
+ setTimeout(debounce_callback, duration);
568
+ last = now;
550
569
  };
551
570
  }
552
571
  export function tomorrow(date) {
@@ -576,17 +595,4 @@ export function nowstr(with_date = false) {
576
595
  const date = new Date();
577
596
  return with_date ? date.to_str() : date.to_time_str();
578
597
  }
579
- /** 面向对象的 timeout, callback 在后的 timeout */
580
- export function set_timeout(delay, callback) {
581
- let ret = {
582
- ref: setTimeout(callback, delay),
583
- clear() {
584
- if (!ret.ref)
585
- return;
586
- clearTimeout(ret.ref);
587
- ret.ref = null;
588
- }
589
- };
590
- return ret;
591
- }
592
598
  //# sourceMappingURL=utils.common.js.map
package/utils.d.ts CHANGED
@@ -45,7 +45,6 @@ export declare function map_stream<Out, In = any>(mapper: (obj: In, cb: Function
45
45
  failures?: boolean;
46
46
  }): Duplex;
47
47
  export declare function stream_to_lines(stream: Readable): AsyncGenerator<string, void, unknown>;
48
- export declare function pipe_with_error<TWritable extends Writable>(readable: Readable, writable: TWritable): TWritable;
49
48
  export declare class WritableMemoryStream extends Writable {
50
49
  chunks: Uint8Array[];
51
50
  pbuffer: import("./utils.common.ts").Deferred<Buffer<ArrayBufferLike>>;
package/utils.js CHANGED
@@ -225,11 +225,6 @@ export async function* stream_to_lines(stream) {
225
225
  buf = chunk.slice(j);
226
226
  }
227
227
  }
228
- export function pipe_with_error(readable, writable) {
229
- // 不知道 transform 作为 AsyncIterable 使用时, emit error 是否会在 for await (...) 循环中触发错误
230
- readable.once('error', error => { writable.emit('error', error); });
231
- return readable.pipe(writable);
232
- }
233
228
  export class WritableMemoryStream extends Writable {
234
229
  chunks = [];
235
230
  pbuffer = defer();