dolphindb 2.0.944 → 2.0.946

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/i18n/dict.json CHANGED
@@ -70,5 +70,8 @@
70
70
  },
71
71
  "连接出错了,可能由于网络原因连接已被关闭,或服务器断开连接": {
72
72
  "en": "connection errored. The connection may have been closed due to network reasons, or the server disconnected"
73
+ },
74
+ "new DdbBlob 不能传空的 value": {
75
+ "en": "new DdbBlob cannot pass an empty value"
73
76
  }
74
77
  }
package/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
2
  import type { InspectOptions as UtilInspectOptions } from 'util';
3
- import { inspect, type WebSocketConnectionError } from 'xshell';
3
+ import { inspect, WebSocket, type WebSocketConnectionError } from 'xshell';
4
4
  export declare enum DdbForm {
5
5
  scalar = 0,
6
6
  vector = 1,
@@ -258,7 +258,7 @@ export declare class DdbObj<TValue extends DdbValue = DdbValue> {
258
258
  /** 自动转换 js string, boolean 为 DdbObj */
259
259
  static to_ddbobj(value: DdbObj | string | boolean): DdbObj;
260
260
  /** 转换 js 数组为 DdbObj[] */
261
- static to_ddbobjs(values: any[]): DdbObj<DdbValue>[];
261
+ static to_ddbobjs(values: (DdbObj | string | boolean)[]): DdbObj<DdbValue>[];
262
262
  to_rows<T extends Record<string, any> = Record<string, any>>(): T[];
263
263
  /** 将 dict<string, any> 自动转换为 js object (Record<string, any>) Automatically convert dict<string, any> to js object (Record<string, any>)
264
264
  - options?:
@@ -337,7 +337,7 @@ export declare class DdbVectorString extends DdbObj<string[]> {
337
337
  constructor(strings: string[], name?: string);
338
338
  }
339
339
  export declare class DdbVectorAny extends DdbObj {
340
- constructor(objs: (DdbObj | string | boolean | ArrayBuffer)[], name?: string);
340
+ constructor(objs: (DdbObj | string | boolean)[], name?: string);
341
341
  }
342
342
  export declare class DdbVectorSymbol extends DdbObj<DdbSymbolExtendedValue> {
343
343
  constructor(strings: string[], name?: string);
@@ -352,8 +352,10 @@ export declare class DdbSetString extends DdbObj<string[]> {
352
352
  constructor(strings: string[] | Set<string>);
353
353
  }
354
354
  /** 构造 DdbDict 对象,支持两种用法: Constructs a DdbDict object, which supports two usages:
355
- - 传入类型是 DdbVectorObj 的 keys, values 两个参数直接组成 dict<keys.type, values.type> 的 DdbDict The incoming type is the keys of DdbObj<DdbVectorValue>, and the two parameters of values directly form the DdbDict of dict<keys.type, values.type>
356
- - 传入 js object (类型是 Record<string, boolean | string | DdbObj>), 自动转换为 dict<string, any> 的 DdbDict Pass in js object (type is Record<string, boolean | string | DdbObj>), automatically converted to DdbDict of dict<string, any> */
355
+ - 传入类型是 DdbVectorObj 的 keys, values 两个参数直接组成 dict<keys.type, values.type> 的 DdbDict
356
+ The incoming type is the keys of DdbObj<DdbVectorValue>, and the two parameters of values directly form the DdbDict of dict<keys.type, values.type>
357
+ - 传入 js object (类型是 Record<string, boolean | string | DdbObj>), 自动转换为 dict<string, any> 的 DdbDict
358
+ Pass in js object (type is Record<string, boolean | string | DdbObj>), automatically converted to DdbDict of dict<string, any> */
357
359
  export declare class DdbDict extends DdbObj<DdbDictValue> {
358
360
  constructor(obj: Record<string, boolean | string | DdbObj>);
359
361
  constructor(keys: DdbVectorObj, values: DdbVectorObj);
@@ -454,7 +456,7 @@ type DdbRpcType = 'script' | 'function' | 'variable' | 'connect';
454
456
  export interface DdbRpcOptions {
455
457
  script?: string;
456
458
  func?: string;
457
- args?: (DdbObj | string | boolean | ArrayBuffer)[];
459
+ args?: (DdbObj | string | boolean)[];
458
460
  vars?: string[];
459
461
  urgent?: boolean;
460
462
  listener?: DdbMessageListener;
@@ -505,12 +507,19 @@ export declare class DDB {
505
507
  print_message: boolean;
506
508
  parse_object: boolean;
507
509
  pnode_run_defined: boolean;
510
+ /** 在 websocket 收到的第一个 error 时,
511
+ 在 connect_websocket 的 on_error 回调中构造 DdbConnectionError 并保存到 DDB 对象上,
512
+ 这个 error 的错误信息最准确
513
+ this.errored 为 true 时建议 throw this.error || new DdbConnectionError(this) */
514
+ error: DdbConnectionError;
508
515
  /** DdbMessage listeners */
509
516
  listeners: DdbMessageListener[];
510
- pconnect: import("xshell").Deferred<void>;
511
- ppnoderun: import("xshell").Deferred<void>;
512
- presult: import("xshell").Deferred<DdbObj<DdbValue>>;
517
+ /** 为所有 websocket 操作加锁,包括设置 this.on_message, this.on_error, websocket.send */
518
+ pwebsocket: import("xshell").Deferred<void>;
519
+ /** 为定义 pnode_run 函数加锁,避免重复定义 */
520
+ ppnode_run: import("xshell").Deferred<void>;
513
521
  get connected(): boolean;
522
+ get errored(): boolean | DdbConnectionError;
514
523
  /**
515
524
  使用 WebSocket URL 初始化连接到 DolphinDB 的实例(不建立实际的网络连接)
516
525
  Initialize an instance of DolphinDB Client using the WebSocket URL (without establishing an actual network connection)
@@ -522,7 +531,7 @@ export declare class DDB {
522
531
  - python?: 设置 python session flag,默认 `false` set python session flag, default `false`
523
532
  - streaming?: 设置该选项后,该 WebSocket 连接只用于流数据 When this option is set, the WebSocket connection is only used for streaming data
524
533
  - verbose?: 是否打印每个 rpc 的信息用于调试
525
-
534
+
526
535
  @example
527
536
  let ddb = new DDB('ws://127.0.0.1:8848')
528
537
 
@@ -544,9 +553,21 @@ export declare class DDB {
544
553
  });
545
554
  private on_message;
546
555
  private on_error;
547
- /** 建立实际的 WebSocket 连接到 URL 对应的 DolphinDB Establish a actual websocket connection to the DolphindB corresponding to the URL
548
- 是幂等的,调用后会确保已连接到数据库 (确保 websocket.readyState 为 open),否则报错 After calling, it will ensure that it has been connected to the database (ensure that websocket.ReadyState is open), otherwise an error will be reported
549
- this.autologin 为 true 时自动登录 this.autologin automatically log in when it is true */
556
+ /** 调用后会确保和数据库的连接是正常的 (this.connected === true && this.errored === false),否则抛出错误
557
+ 这个方法是幂等的,首次调用建立实际的 WebSocket 连接到 URL 对应的 DolphinDB,然后执行自动登录,
558
+ 如果是流数据连接,还会调用 publishTable 订阅流表
559
+ 后续调用检查上面的条件
560
+ 连接断开后禁止再次调用 connect 重连原有 ddb 对象,应该通过 new DDB() 的方式新建连接对象,原因是:
561
+ 1. on_error 回调和某个 websocket 绑定了,不方便解绑后重新绑定
562
+ 2. session 是有状态的,重连也无法恢复之前的状态
563
+ 3. 断线后所有之前的 ddb.call, ddb.eval 都应该抛出连接错误
564
+
565
+ After calling, it will ensure that the connection with the database is normal (this.connected === true && this.errored === false), otherwise an error will be thrown
566
+ This method is idempotent, the first call establishes an actual WebSocket connection to the DolphinDB corresponding to the URL, and subsequent calls check the above conditions
567
+ After the connection is disconnected, it is forbidden to call connect again to reconnect the original ddb object. You should use new DDB() to create a new connection object because:
568
+ 1. The on_error callback is bound to a certain websocket, and it is inconvenient to unbind and rebind
569
+ 2. The session is stateful, and the previous state cannot be restored even after reconnection
570
+ 3. After disconnection, all previous ddb.call, ddb.eval should throw a connection error */
550
571
  connect(): Promise<void>;
551
572
  get_rpc_options({ urgent, secondary, async: _async, pickle, clear, api, compress, cancellable, priority, parallelism, root_id, limit, }?: {
552
573
  urgent?: boolean;
@@ -572,10 +593,10 @@ export declare class DDB {
572
593
  limit?: boolean;
573
594
  }): string;
574
595
  disconnect(): void;
575
- /** rpc through websocket (function/script/variable command)
596
+ /** (内部使用的方法) rpc through websocket (function/script/variable command)
576
597
  未连接到 DDB 时调用会自动连接,连接断开时调用会抛出 DdbConnectionError
577
598
  When the DDB is not connected, the call will be automatically connected. When the connection is disconnected, the call will throw the DdbConnectionError
578
- - type: API 类型: 'script' | 'function' | 'variable'
599
+ - type: API 类型: 'script' | 'function' | 'variable' | 'connect'
579
600
  - options:
580
601
  - urgent?: 决定 `行为标识` 那一行字符串的取值(只适用于 script 和 function)
581
602
  - vars?: type === 'variable' 时必传,variable 指令中待上传的变量名
@@ -623,7 +644,7 @@ export declare class DDB {
623
644
  When it is false, the returned DdbObj only contains buffer and le without parsing,
624
645
  so as to facilitate subsequent forwarding and serialization
625
646
  */
626
- call<TResult extends DdbObj>(func: string, args?: (DdbObj | string | boolean | ArrayBuffer)[], { urgent, node, nodes, func_type, add_node_alias, listener, parse_object, }?: {
647
+ call<TResult extends DdbObj>(func: string, args?: (DdbObj | string | boolean)[], { urgent, node, nodes, func_type, add_node_alias, listener, parse_object, }?: {
627
648
  urgent?: boolean;
628
649
  node?: string;
629
650
  nodes?: string[];
@@ -661,7 +682,7 @@ export interface DdbObjectMessage {
661
682
  }
662
683
  export interface DdbErrorMessage {
663
684
  type: 'error';
664
- data: Error;
685
+ data: DdbDatabaseError;
665
686
  }
666
687
  export type DdbMessage = DdbPrintMessage | DdbObjectMessage | DdbErrorMessage;
667
688
  export {};
package/index.js CHANGED
@@ -1677,6 +1677,7 @@ export class DdbNanoTimeStamp extends DdbObj {
1677
1677
  }
1678
1678
  export class DdbBlob extends DdbObj {
1679
1679
  constructor(value) {
1680
+ assert(value, t('new DdbBlob 不能传空的 value'));
1680
1681
  super({
1681
1682
  form: DdbForm.scalar,
1682
1683
  type: DdbType.blob,
@@ -1865,8 +1866,10 @@ export class DdbSetString extends DdbObj {
1865
1866
  }
1866
1867
  }
1867
1868
  /** 构造 DdbDict 对象,支持两种用法: Constructs a DdbDict object, which supports two usages:
1868
- - 传入类型是 DdbVectorObj 的 keys, values 两个参数直接组成 dict<keys.type, values.type> 的 DdbDict The incoming type is the keys of DdbObj<DdbVectorValue>, and the two parameters of values directly form the DdbDict of dict<keys.type, values.type>
1869
- - 传入 js object (类型是 Record<string, boolean | string | DdbObj>), 自动转换为 dict<string, any> 的 DdbDict Pass in js object (type is Record<string, boolean | string | DdbObj>), automatically converted to DdbDict of dict<string, any> */
1869
+ - 传入类型是 DdbVectorObj 的 keys, values 两个参数直接组成 dict<keys.type, values.type> 的 DdbDict
1870
+ The incoming type is the keys of DdbObj<DdbVectorValue>, and the two parameters of values directly form the DdbDict of dict<keys.type, values.type>
1871
+ - 传入 js object (类型是 Record<string, boolean | string | DdbObj>), 自动转换为 dict<string, any> 的 DdbDict
1872
+ Pass in js object (type is Record<string, boolean | string | DdbObj>), automatically converted to DdbDict of dict<string, any> */
1870
1873
  export class DdbDict extends DdbObj {
1871
1874
  constructor(arg0, arg1) {
1872
1875
  if (arg1)
@@ -2193,13 +2196,22 @@ export class DDB {
2193
2196
  print_message = true;
2194
2197
  parse_object = true;
2195
2198
  pnode_run_defined = false;
2199
+ /** 在 websocket 收到的第一个 error 时,
2200
+ 在 connect_websocket 的 on_error 回调中构造 DdbConnectionError 并保存到 DDB 对象上,
2201
+ 这个 error 的错误信息最准确
2202
+ this.errored 为 true 时建议 throw this.error || new DdbConnectionError(this) */
2203
+ error;
2196
2204
  /** DdbMessage listeners */
2197
2205
  listeners = [];
2198
- pconnect = defer(null);
2199
- ppnoderun = defer(null);
2200
- presult = defer(null);
2206
+ /** 为所有 websocket 操作加锁,包括设置 this.on_message, this.on_error, websocket.send */
2207
+ pwebsocket = defer(null);
2208
+ /** 为定义 pnode_run 函数加锁,避免重复定义 */
2209
+ ppnode_run = defer(null);
2201
2210
  get connected() {
2202
- return this.websocket?.readyState === WebSocket.OPEN;
2211
+ return !this.error && this.websocket?.readyState === WebSocket.OPEN;
2212
+ }
2213
+ get errored() {
2214
+ return this.error || (this.websocket && this.websocket.readyState !== WebSocket.OPEN);
2203
2215
  }
2204
2216
  /**
2205
2217
  使用 WebSocket URL 初始化连接到 DolphinDB 的实例(不建立实际的网络连接)
@@ -2212,7 +2224,7 @@ export class DDB {
2212
2224
  - python?: 设置 python session flag,默认 `false` set python session flag, default `false`
2213
2225
  - streaming?: 设置该选项后,该 WebSocket 连接只用于流数据 When this option is set, the WebSocket connection is only used for streaming data
2214
2226
  - verbose?: 是否打印每个 rpc 的信息用于调试
2215
-
2227
+
2216
2228
  @example
2217
2229
  let ddb = new DDB('ws://127.0.0.1:8848')
2218
2230
 
@@ -2240,61 +2252,87 @@ export class DDB {
2240
2252
  this.streaming = options.streaming;
2241
2253
  }
2242
2254
  on_message(buffer, websocket) {
2243
- // 这里的实现一定会被 connect, rpc 中的实现覆盖
2255
+ throw t('这是在调用 this.rpc 之前默认的 on_message, 不应该被调用到,除非建立连接后 server 先推送了 message');
2244
2256
  }
2245
- on_error(error, websocket) {
2257
+ on_error() {
2246
2258
  // 这里的实现一定会被 connect, rpc 中的实现覆盖
2247
2259
  }
2248
- /** 建立实际的 WebSocket 连接到 URL 对应的 DolphinDB Establish a actual websocket connection to the DolphindB corresponding to the URL
2249
- 是幂等的,调用后会确保已连接到数据库 (确保 websocket.readyState 为 open),否则报错 After calling, it will ensure that it has been connected to the database (ensure that websocket.ReadyState is open), otherwise an error will be reported
2250
- this.autologin 为 true 时自动登录 this.autologin automatically log in when it is true */
2260
+ /** 调用后会确保和数据库的连接是正常的 (this.connected === true && this.errored === false),否则抛出错误
2261
+ 这个方法是幂等的,首次调用建立实际的 WebSocket 连接到 URL 对应的 DolphinDB,然后执行自动登录,
2262
+ 如果是流数据连接,还会调用 publishTable 订阅流表
2263
+ 后续调用检查上面的条件
2264
+ 连接断开后禁止再次调用 connect 重连原有 ddb 对象,应该通过 new DDB() 的方式新建连接对象,原因是:
2265
+ 1. on_error 回调和某个 websocket 绑定了,不方便解绑后重新绑定
2266
+ 2. session 是有状态的,重连也无法恢复之前的状态
2267
+ 3. 断线后所有之前的 ddb.call, ddb.eval 都应该抛出连接错误
2268
+
2269
+ After calling, it will ensure that the connection with the database is normal (this.connected === true && this.errored === false), otherwise an error will be thrown
2270
+ This method is idempotent, the first call establishes an actual WebSocket connection to the DolphinDB corresponding to the URL, and subsequent calls check the above conditions
2271
+ After the connection is disconnected, it is forbidden to call connect again to reconnect the original ddb object. You should use new DDB() to create a new connection object because:
2272
+ 1. The on_error callback is bound to a certain websocket, and it is inconvenient to unbind and rebind
2273
+ 2. The session is stateful, and the previous state cannot be restored even after reconnection
2274
+ 3. After disconnection, all previous ddb.call, ddb.eval should throw a connection error */
2251
2275
  async connect() {
2276
+ if (this.errored)
2277
+ throw this.error || new DdbConnectionError(this);
2252
2278
  if (this.connected)
2253
2279
  return;
2254
- const ptail = this.pconnect;
2255
- const pconnect = this.pconnect = defer();
2256
- await ptail;
2280
+ // 准备进入 websocket 临界区
2281
+ const ptail = this.pwebsocket;
2282
+ let pwebsocket = this.pwebsocket = defer();
2283
+ // 避免 pwebsocket.reject 后出现 triggerUncaughtException
2284
+ pwebsocket.catch(() => { });
2285
+ // 上一个请求出现了 websocket 错误,那么直接抛出异常,且通过 reject 当前锁 (pwebsocket),安全退出临界区
2257
2286
  try {
2258
- if (!this.connected) {
2259
- this.on_error = (error, websocket) => {
2260
- pconnect.reject(new DdbConnectionError(this, error));
2261
- };
2262
- this.on_message = (buffer, websocket) => {
2263
- assert(false, t('这是在调用 this.rpc 之前默认的 on_message, 不应该被调用到,除非建立连接后 server 先推送了 message'));
2264
- };
2265
- this.presult = defer(null);
2266
- this.pnode_run_defined = false;
2267
- try {
2268
- this.websocket = await connect_websocket(this.url, {
2269
- protocols: (() => {
2270
- if (this.streaming)
2271
- return 'streaming';
2272
- if (this.python)
2273
- return 'python';
2274
- })(),
2275
- on_message: (buffer, websocket) => {
2276
- this.on_message(buffer, websocket);
2277
- },
2278
- on_error: (error, websocket) => {
2279
- this.on_error(error, websocket);
2280
- }
2281
- });
2282
- }
2283
- catch (error) {
2284
- throw new DdbConnectionError(this, error);
2285
- }
2286
- if (this.streaming)
2287
- await this.subscribe();
2288
- else {
2289
- await this.rpc('connect', {});
2290
- if (this.autologin)
2291
- await this.call('login', [this.username, this.password], { urgent: true });
2287
+ await ptail;
2288
+ // 已进入 websocket 临界区
2289
+ }
2290
+ catch (error) {
2291
+ pwebsocket.reject(error);
2292
+ throw error;
2293
+ }
2294
+ if (this.errored) {
2295
+ const error = this.error || new DdbConnectionError(this);
2296
+ pwebsocket.reject(error);
2297
+ throw error;
2298
+ }
2299
+ if (this.connected) {
2300
+ pwebsocket.resolve();
2301
+ return;
2302
+ }
2303
+ this.on_error = () => {
2304
+ pwebsocket.reject(this.error /* 一定有,不需要再 || new DdbConnectionError(this) */);
2305
+ };
2306
+ try {
2307
+ this.websocket = await connect_websocket(this.url, {
2308
+ protocols: (() => {
2309
+ if (this.streaming)
2310
+ return 'streaming';
2311
+ if (this.python)
2312
+ return 'python';
2313
+ })(),
2314
+ on_message: (buffer, websocket) => {
2315
+ this.on_message(buffer, websocket);
2316
+ },
2317
+ on_error: error => {
2318
+ this.error ??= new DdbConnectionError(this, error);
2319
+ this.on_error();
2292
2320
  }
2293
- }
2321
+ });
2294
2322
  }
2295
- finally {
2296
- pconnect.resolve();
2323
+ catch (error) {
2324
+ this.error ??= new DdbConnectionError(this, error);
2325
+ pwebsocket.reject(error);
2326
+ throw this.error;
2297
2327
  }
2328
+ assert(this.connected);
2329
+ pwebsocket.resolve();
2330
+ // websocket 临界区结束
2331
+ await this.rpc('connect', {});
2332
+ if (this.autologin)
2333
+ await this.call('login', [this.username, this.password], { urgent: true });
2334
+ if (this.streaming)
2335
+ await this.subscribe();
2298
2336
  }
2299
2337
  get_rpc_options({ urgent = false, secondary = false, async: _async = false, pickle = false, clear = false, api = false, compress = false, cancellable = true, priority = urgent ? 8 : 4, parallelism = 8, root_id = '', limit, } = {}) {
2300
2338
  let flag = 0;
@@ -2331,10 +2369,10 @@ export class DDB {
2331
2369
  if (this.connected)
2332
2370
  this.websocket.close(1000);
2333
2371
  }
2334
- /** rpc through websocket (function/script/variable command)
2372
+ /** (内部使用的方法) rpc through websocket (function/script/variable command)
2335
2373
  未连接到 DDB 时调用会自动连接,连接断开时调用会抛出 DdbConnectionError
2336
2374
  When the DDB is not connected, the call will be automatically connected. When the connection is disconnected, the call will throw the DdbConnectionError
2337
- - type: API 类型: 'script' | 'function' | 'variable'
2375
+ - type: API 类型: 'script' | 'function' | 'variable' | 'connect'
2338
2376
  - options:
2339
2377
  - urgent?: 决定 `行为标识` 那一行字符串的取值(只适用于 script 和 function)
2340
2378
  - vars?: type === 'variable' 时必传,variable 指令中待上传的变量名
@@ -2342,19 +2380,27 @@ export class DDB {
2342
2380
  - parse_object?: 在本次 rpc 期间设置 parse_object, 结束后恢复原有
2343
2381
  为 false 时返回的 DdbObj 仅含有 buffer 和 le,不做解析,以便后续转发、序列化 */
2344
2382
  async rpc(type, options) {
2345
- if (!this.websocket)
2346
- await this.connect();
2347
- if (!this.connected)
2348
- throw new DdbConnectionError(this);
2349
- const { script, func, vars = [], urgent, listener, } = options;
2350
- let { args = [] } = options;
2383
+ // 保留调用栈信息
2384
+ let error = new DdbDatabaseError('', this, type, options);
2385
+ await this.connect();
2386
+ const { script, func, args: _args = [], vars = [], urgent, listener, } = options;
2351
2387
  if (func === 'pnode_run' && !this.pnode_run_defined) {
2352
- // 保证并发调用 rpc 时只定义一次 pnode_run
2353
- const ptail = this.ppnoderun;
2354
- const ppnoderun = this.ppnoderun = defer();
2355
- await ptail;
2388
+ // 准备进入 pnode_run 临界区,保证并发调用 rpc 时只定义一次 pnode_run
2389
+ const ptail = this.ppnode_run;
2390
+ let ppnode_run = this.ppnode_run = defer();
2391
+ // 避免 ppnode_run.reject 后出现 triggerUncaughtException
2392
+ ppnode_run.catch(() => { });
2393
+ // 如果之前的定义失败了,再定义一次也会失败,直接抛出之前的错误,且安全地退出临界区
2356
2394
  try {
2357
- if (!this.pnode_run_defined)
2395
+ await ptail;
2396
+ // 已进入 pnode_run 临界区
2397
+ }
2398
+ catch (error) {
2399
+ ppnode_run.reject(error);
2400
+ throw error;
2401
+ }
2402
+ if (!this.pnode_run_defined)
2403
+ try {
2358
2404
  await this.eval(this.python ?
2359
2405
  'def pnode_run (nodes, func_name, args, add_node_alias):\n' +
2360
2406
  ' nargs = size(args)\n' +
@@ -2390,95 +2436,108 @@ export class DDB {
2390
2436
  ' add_node_alias\n' +
2391
2437
  ' )\n' +
2392
2438
  '}\n', { urgent: true });
2393
- }
2394
- finally {
2395
- ppnoderun.resolve();
2396
- }
2439
+ this.pnode_run_defined = true;
2440
+ ppnode_run.resolve();
2441
+ }
2442
+ catch (error) {
2443
+ // 这次失败了,之后的执行肯定也会失败
2444
+ ppnode_run.reject(error);
2445
+ throw error;
2446
+ }
2397
2447
  }
2398
2448
  // this 上的当前配置需要在 message 到达后使用,先保存起来
2399
- const _handlers = [...this.listeners].reverse();
2400
- let error = new DdbDatabaseError('', this, type, options);
2401
- // 临界区:保证多个 rpc 并发时形成 promise 链
2449
+ const _listeners = [...this.listeners].reverse();
2450
+ // websocket 临界区:保证多个 rpc 并发时形成 promise 链
2402
2451
  // ddb 世界观:需要等待上一个 rpc 结果从 server 返回之后才能发起下一个调用
2403
2452
  // 违反世界观可能造成:
2404
2453
  // 1. 并发多个请求只返回第一个结果(阻塞,需后续请求疏通)
2405
2454
  // 2. windows 下 ddb server 返回多个相同的结果
2406
- const ptail = this.presult;
2407
- const presult = this.presult = defer();
2455
+ const ptail = this.pwebsocket;
2456
+ let pwebsocket = this.pwebsocket = defer();
2457
+ // 避免 pwebsocket.reject 后出现 triggerUncaughtException
2458
+ pwebsocket.catch(() => { });
2459
+ // 上一个请求出现了 websocket 错误,那么直接抛出异常,且通过 reject 当前锁 (pwebsocket),安全退出临界区
2408
2460
  try {
2409
2461
  await ptail;
2462
+ // 已进入临界区,只有一个 rpc 函数调用运行到这里,可以独占 this.on_message 然后写 WebSocket
2410
2463
  }
2411
- catch { }
2412
- // 临界区结束,只有一个 rpc 函数调用运行到这里,可以独占 this.on_message 然后写 WebSocket
2413
- if (!this.connected) {
2414
- presult.reject(new DdbConnectionError(this));
2415
- return presult;
2464
+ catch (error) {
2465
+ pwebsocket.reject(error);
2466
+ throw error;
2416
2467
  }
2417
- this.on_error = (error, websocket) => {
2418
- presult.reject(new DdbConnectionError(this, error));
2419
- };
2420
- this.on_message = buffer => {
2421
- try {
2422
- const buf = new Uint8Array(buffer);
2423
- if (this.print_message_buffer)
2424
- console.log(typed_array_to_buffer(buf));
2425
- const message = this.parse_message(buf, error);
2426
- listener?.(message, this);
2427
- for (const listener of _handlers)
2428
- listener(message, this);
2429
- const { type, data } = message;
2468
+ // 既然上一个请求没有出现 websocket error,且函数开头已经调用了 await this.connect() 检查过,
2469
+ // 这里也乐观的认为 this.connected && !this.errored 为 true
2470
+ return new Promise((resolve, reject) => {
2471
+ this.on_error = () => {
2472
+ pwebsocket.reject(this.error /* 这里一定有了 this.error, 不需要再 || new DdbConnectionError(this) */);
2473
+ reject(this.error);
2474
+ };
2475
+ this.on_message = buffer => {
2476
+ try {
2477
+ const buf = new Uint8Array(buffer);
2478
+ if (this.print_message_buffer)
2479
+ console.log(typed_array_to_buffer(buf));
2480
+ const message = this.parse_message(buf, error);
2481
+ listener?.(message, this);
2482
+ for (const listener of _listeners)
2483
+ listener(message, this);
2484
+ const { type, data } = message;
2485
+ switch (type) {
2486
+ case 'print':
2487
+ if (this.print_message)
2488
+ console.log(data);
2489
+ break;
2490
+ case 'object':
2491
+ pwebsocket.resolve();
2492
+ resolve(data);
2493
+ break;
2494
+ case 'error':
2495
+ pwebsocket.resolve();
2496
+ reject(data);
2497
+ break;
2498
+ }
2499
+ }
2500
+ catch (error) {
2501
+ pwebsocket.resolve();
2502
+ // 这里的错误并非 websocket 错误,而是 rpc 错误
2503
+ reject(error);
2504
+ }
2505
+ };
2506
+ const args = DdbObj.to_ddbobjs(_args);
2507
+ const command = this.enc.encode((() => {
2430
2508
  switch (type) {
2431
- case 'print':
2432
- if (this.print_message)
2433
- console.log(data);
2434
- return;
2435
- case 'object':
2436
- presult.resolve(data);
2437
- return;
2438
- case 'error':
2439
- presult.reject(data);
2440
- return;
2509
+ case 'function':
2510
+ if (this.verbose)
2511
+ console.log(func + inspect(args, { colors: false }));
2512
+ return 'function\n' +
2513
+ `${func}\n` +
2514
+ `${args.length}\n` +
2515
+ `${Number(DDB.le_client)}\n`;
2516
+ case 'script':
2517
+ if (this.verbose)
2518
+ console.log(script);
2519
+ return 'script\n' +
2520
+ script;
2521
+ case 'variable':
2522
+ if (this.verbose)
2523
+ for (let i = 0; i < vars.length; i++)
2524
+ console.log(`${vars[i]} = ${inspect(args[i], { colors: false })}`);
2525
+ return 'variable\n' +
2526
+ `${vars.join(',')}\n` +
2527
+ `${vars.length}\n` +
2528
+ `${Number(DDB.le_client)}\n`;
2529
+ case 'connect':
2530
+ if (this.verbose)
2531
+ console.log('connect()');
2532
+ return 'connect\n';
2441
2533
  }
2442
- }
2443
- catch (error) {
2444
- presult.reject(error);
2445
- }
2446
- };
2447
- args = DdbObj.to_ddbobjs(args);
2448
- const command = this.enc.encode((() => {
2449
- switch (type) {
2450
- case 'function':
2451
- if (this.verbose)
2452
- console.log(func + args.map(arg => inspect(arg, { colors: false })).join(', ').bracket());
2453
- return 'function\n' +
2454
- `${func}\n` +
2455
- `${args.length}\n` +
2456
- `${Number(DDB.le_client)}\n`;
2457
- case 'script':
2458
- if (this.verbose)
2459
- console.log(script);
2460
- return 'script\n' +
2461
- script;
2462
- case 'variable':
2463
- if (this.verbose)
2464
- for (let i = 0; i < vars.length; i++)
2465
- console.log(`${vars[i]} = ${inspect(args[i], { colors: false })}`);
2466
- return 'variable\n' +
2467
- `${vars.join(',')}\n` +
2468
- `${vars.length}\n` +
2469
- `${Number(DDB.le_client)}\n`;
2470
- case 'connect':
2471
- if (this.verbose)
2472
- console.log('connect()');
2473
- return 'connect\n';
2474
- }
2475
- })());
2476
- this.websocket.send(concat([
2477
- this.enc.encode(`API2 ${this.sid} ${command.length} / ${this.get_rpc_options({ urgent })}\n`),
2478
- command,
2479
- ...args.map((arg) => arg.pack())
2480
- ]));
2481
- return presult;
2534
+ })());
2535
+ this.websocket.send(concat([
2536
+ this.enc.encode(`API2 ${this.sid} ${command.length} / ${this.get_rpc_options({ urgent })}\n`),
2537
+ command,
2538
+ ...args.map(arg => arg.pack())
2539
+ ]));
2540
+ });
2482
2541
  }
2483
2542
  /** eval script through websocket (script command)
2484
2543
  - script?: 执行的脚本 Script to execute
@@ -2630,9 +2689,6 @@ export class DDB {
2630
2689
  }
2631
2690
  /** 内部的流订阅方法 Internal stream subscription method */
2632
2691
  async subscribe() {
2633
- await this.rpc('connect', {});
2634
- if (this.autologin)
2635
- await this.call('login', [this.username, this.password]);
2636
2692
  console.log(t('订阅流表成功') + ', colnames:',
2637
2693
  // string[3](['time', 'stock', 'price'])
2638
2694
  this.streaming.colnames = (await this.call('publishTable', [