@naturalcycles/js-lib 15.93.1 → 15.93.2

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.
@@ -52,7 +52,13 @@ export declare class PGradualQueue {
52
52
  * Push PromiseReturningFunction to the Queue.
53
53
  * Returns a Promise that resolves (or rejects) with the return value from the Promise.
54
54
  */
55
- push<R>(fn_: AsyncFunction<R>): Promise<R>;
55
+ push<R>(fn: AsyncFunction<R>): Promise<R>;
56
+ /**
57
+ * Starts the job and settles its `defer`.
58
+ * Intentionally not async: it's called both from `push` and when a running job finishes,
59
+ * and must not create a Promise that nobody handles (which would cause UnhandledRejection).
60
+ */
61
+ private start;
56
62
  get queueSize(): number;
57
63
  /**
58
64
  * Current concurrency limit based on warmup progress.
@@ -48,48 +48,55 @@ export class PGradualQueue {
48
48
  * Push PromiseReturningFunction to the Queue.
49
49
  * Returns a Promise that resolves (or rejects) with the return value from the Promise.
50
50
  */
51
- async push(fn_) {
51
+ async push(fn) {
52
52
  // Initialize start time on first push
53
53
  if (this.startTime === 0) {
54
54
  this.startTime = Date.now();
55
55
  }
56
- const { logger } = this;
57
- const fn = fn_;
58
- fn.defer ||= pDefer();
56
+ const job = { fn, defer: pDefer() };
59
57
  const concurrency = this.getCurrentConcurrency();
60
58
  if (this.inFlight < concurrency) {
61
- this.inFlight++;
62
- logger.debug(`inFlight++ ${this.inFlight}/${concurrency}, queue ${this.queue.length}`);
63
- runSafe(fn)
64
- .then(result => {
65
- fn.defer.resolve(result);
66
- })
67
- .catch((err) => {
68
- if (this.cfg.errorMode === ErrorMode.SUPPRESS) {
69
- logger.error(err);
70
- fn.defer.resolve(); // resolve with `void`
71
- }
72
- else {
73
- fn.defer.reject(err);
74
- }
75
- })
76
- .finally(() => {
77
- this.inFlight--;
78
- const currentConcurrency = this.getCurrentConcurrency();
79
- logger.debug(`inFlight-- ${this.inFlight}/${currentConcurrency}, queue ${this.queue.length}`);
80
- // Start queued jobs up to the current concurrency limit
81
- // Use while loop since concurrency may have increased during warmup
82
- while (this.queue.length && this.inFlight < this.getCurrentConcurrency()) {
83
- const nextFn = this.queue.shift();
84
- void this.push(nextFn);
85
- }
86
- });
59
+ this.start(job, concurrency);
87
60
  }
88
61
  else {
89
- this.queue.push(fn);
90
- logger.debug(`inFlight ${this.inFlight}/${concurrency}, queue++ ${this.queue.length}`);
62
+ this.queue.push(job);
63
+ this.logger.debug(`inFlight ${this.inFlight}/${concurrency}, queue++ ${this.queue.length}`);
91
64
  }
92
- return await fn.defer;
65
+ return await job.defer;
66
+ }
67
+ /**
68
+ * Starts the job and settles its `defer`.
69
+ * Intentionally not async: it's called both from `push` and when a running job finishes,
70
+ * and must not create a Promise that nobody handles (which would cause UnhandledRejection).
71
+ */
72
+ start(job, concurrency) {
73
+ const { logger } = this;
74
+ const { defer } = job;
75
+ this.inFlight++;
76
+ logger.debug(`inFlight++ ${this.inFlight}/${concurrency}, queue ${this.queue.length}`);
77
+ runSafe(job.fn)
78
+ .then(result => {
79
+ defer.resolve(result);
80
+ })
81
+ .catch((err) => {
82
+ if (this.cfg.errorMode === ErrorMode.SUPPRESS) {
83
+ logger.error(err);
84
+ defer.resolve(); // resolve with `void`
85
+ }
86
+ else {
87
+ defer.reject(err);
88
+ }
89
+ })
90
+ .finally(() => {
91
+ this.inFlight--;
92
+ const currentConcurrency = this.getCurrentConcurrency();
93
+ logger.debug(`inFlight-- ${this.inFlight}/${currentConcurrency}, queue ${this.queue.length}`);
94
+ // Start queued jobs up to the current concurrency limit
95
+ // Use while loop since concurrency may have increased during warmup
96
+ while (this.queue.length && this.inFlight < currentConcurrency) {
97
+ this.start(this.queue.shift(), currentConcurrency);
98
+ }
99
+ });
93
100
  }
94
101
  get queueSize() {
95
102
  return this.queue.length;
@@ -49,7 +49,13 @@ export declare class PQueue {
49
49
  * Push PromiseReturningFunction to the Queue.
50
50
  * Returns a Promise that resolves (or rejects) with the return value from the Promise.
51
51
  */
52
- push<R>(fn_: AsyncFunction<R>): Promise<R>;
52
+ push<R>(fn: AsyncFunction<R>): Promise<R>;
53
+ /**
54
+ * Starts the job and settles its `defer`.
55
+ * Intentionally not async: it's called both from `push` and when a running job finishes,
56
+ * and must not create a Promise that nobody handles (which would cause UnhandledRejection).
57
+ */
58
+ private start;
53
59
  get queueSize(): number;
54
60
  /**
55
61
  * Returns a Promise that resolves when the queue is Idle (next time, since the call).
@@ -28,59 +28,66 @@ export class PQueue {
28
28
  * Push PromiseReturningFunction to the Queue.
29
29
  * Returns a Promise that resolves (or rejects) with the return value from the Promise.
30
30
  */
31
- async push(fn_) {
32
- const { concurrency } = this.cfg;
33
- const { resolveOnStart, logger } = this;
34
- const fn = fn_;
35
- fn.defer ||= pDefer();
36
- if (this.inFlight < concurrency) {
31
+ async push(fn) {
32
+ const job = { fn, defer: pDefer() };
33
+ if (this.inFlight < this.cfg.concurrency) {
37
34
  // There is room for more jobs. Can start immediately
38
- this.inFlight++;
39
- logger.debug(`inFlight++ ${this.inFlight}/${concurrency}, queue ${this.queue.length}`);
40
- if (resolveOnStart)
41
- fn.defer.resolve();
42
- runSafe(fn)
43
- .then(result => {
44
- if (!resolveOnStart)
45
- fn.defer.resolve(result);
46
- })
47
- .catch((err) => {
48
- if (resolveOnStart) {
49
- logger.error(err);
50
- return;
51
- }
52
- if (this.cfg.errorMode === ErrorMode.SUPPRESS) {
53
- logger.error(err);
54
- fn.defer.resolve(); // resolve with `void`
55
- }
56
- else {
57
- // Should be handled on the outside, otherwise it'll cause UnhandledRejection
58
- // Not logging, because it's re-thrown upstream
59
- fn.defer.reject(err);
60
- }
61
- })
62
- .finally(() => {
63
- this.inFlight--;
64
- logger.debug(`inFlight-- ${this.inFlight}/${concurrency}, queue ${this.queue.length}`);
65
- // check if there's room to start next job
66
- if (this.queue.length && this.inFlight <= concurrency) {
67
- const nextFn = this.queue.shift();
68
- void this.push(nextFn);
69
- }
70
- else {
71
- if (this.inFlight === 0) {
72
- logger.debug('onIdle');
73
- this.onIdleListeners.forEach(defer => defer.resolve());
74
- this.onIdleListeners.length = 0; // empty the array
75
- }
76
- }
77
- });
35
+ this.start(job);
78
36
  }
79
37
  else {
80
- this.queue.push(fn);
81
- logger.debug(`inFlight ${this.inFlight}/${concurrency}, queue++ ${this.queue.length}`);
38
+ this.queue.push(job);
39
+ this.logger.debug(`inFlight ${this.inFlight}/${this.cfg.concurrency}, queue++ ${this.queue.length}`);
82
40
  }
83
- return await fn.defer;
41
+ return await job.defer;
42
+ }
43
+ /**
44
+ * Starts the job and settles its `defer`.
45
+ * Intentionally not async: it's called both from `push` and when a running job finishes,
46
+ * and must not create a Promise that nobody handles (which would cause UnhandledRejection).
47
+ */
48
+ start(job) {
49
+ const { concurrency } = this.cfg;
50
+ const { resolveOnStart, logger } = this;
51
+ const { defer } = job;
52
+ this.inFlight++;
53
+ logger.debug(`inFlight++ ${this.inFlight}/${concurrency}, queue ${this.queue.length}`);
54
+ if (resolveOnStart)
55
+ defer.resolve();
56
+ runSafe(job.fn)
57
+ .then(result => {
58
+ if (!resolveOnStart)
59
+ defer.resolve(result);
60
+ })
61
+ .catch((err) => {
62
+ if (resolveOnStart) {
63
+ logger.error(err);
64
+ return;
65
+ }
66
+ if (this.cfg.errorMode === ErrorMode.SUPPRESS) {
67
+ logger.error(err);
68
+ defer.resolve(); // resolve with `void`
69
+ }
70
+ else {
71
+ // Should be handled on the outside, otherwise it'll cause UnhandledRejection
72
+ // Not logging, because it's re-thrown upstream
73
+ defer.reject(err);
74
+ }
75
+ })
76
+ .finally(() => {
77
+ this.inFlight--;
78
+ logger.debug(`inFlight-- ${this.inFlight}/${concurrency}, queue ${this.queue.length}`);
79
+ // check if there's room to start next job
80
+ if (this.queue.length && this.inFlight < concurrency) {
81
+ this.start(this.queue.shift());
82
+ }
83
+ else {
84
+ if (this.inFlight === 0) {
85
+ logger.debug('onIdle');
86
+ this.onIdleListeners.forEach(defer => defer.resolve());
87
+ this.onIdleListeners.length = 0; // empty the array
88
+ }
89
+ }
90
+ });
84
91
  }
85
92
  get queueSize() {
86
93
  return this.queue.length;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
3
  "type": "module",
4
- "version": "15.93.1",
4
+ "version": "15.93.2",
5
5
  "dependencies": {
6
6
  "tslib": "^2"
7
7
  },
@@ -63,7 +63,7 @@ export class PGradualQueue {
63
63
  private warmupComplete: boolean
64
64
 
65
65
  inFlight = 0
66
- private queue: AsyncFunction[] = []
66
+ private queue: PGradualQueueJob<any>[] = []
67
67
 
68
68
  /**
69
69
  * Get current allowed concurrency based on warmup progress.
@@ -89,54 +89,62 @@ export class PGradualQueue {
89
89
  * Push PromiseReturningFunction to the Queue.
90
90
  * Returns a Promise that resolves (or rejects) with the return value from the Promise.
91
91
  */
92
- async push<R>(fn_: AsyncFunction<R>): Promise<R> {
92
+ async push<R>(fn: AsyncFunction<R>): Promise<R> {
93
93
  // Initialize start time on first push
94
94
  if (this.startTime === 0) {
95
95
  this.startTime = Date.now()
96
96
  }
97
97
 
98
- const { logger } = this
99
- const fn = fn_ as AsyncFunctionWithDefer<R>
100
- fn.defer ||= pDefer<R>()
101
-
98
+ const job: PGradualQueueJob<R> = { fn, defer: pDefer<R>() }
102
99
  const concurrency = this.getCurrentConcurrency()
103
100
 
104
101
  if (this.inFlight < concurrency) {
105
- this.inFlight++
106
- logger.debug(`inFlight++ ${this.inFlight}/${concurrency}, queue ${this.queue.length}`)
107
-
108
- runSafe(fn)
109
- .then(result => {
110
- fn.defer.resolve(result)
111
- })
112
- .catch((err: Error) => {
113
- if (this.cfg.errorMode === ErrorMode.SUPPRESS) {
114
- logger.error(err)
115
- fn.defer.resolve() // resolve with `void`
116
- } else {
117
- fn.defer.reject(err)
118
- }
119
- })
120
- .finally(() => {
121
- this.inFlight--
122
- const currentConcurrency = this.getCurrentConcurrency()
123
- logger.debug(
124
- `inFlight-- ${this.inFlight}/${currentConcurrency}, queue ${this.queue.length}`,
125
- )
126
-
127
- // Start queued jobs up to the current concurrency limit
128
- // Use while loop since concurrency may have increased during warmup
129
- while (this.queue.length && this.inFlight < this.getCurrentConcurrency()) {
130
- const nextFn = this.queue.shift()!
131
- void this.push(nextFn)
132
- }
133
- })
102
+ this.start(job, concurrency)
134
103
  } else {
135
- this.queue.push(fn)
136
- logger.debug(`inFlight ${this.inFlight}/${concurrency}, queue++ ${this.queue.length}`)
104
+ this.queue.push(job)
105
+ this.logger.debug(`inFlight ${this.inFlight}/${concurrency}, queue++ ${this.queue.length}`)
137
106
  }
138
107
 
139
- return await fn.defer
108
+ return await job.defer
109
+ }
110
+
111
+ /**
112
+ * Starts the job and settles its `defer`.
113
+ * Intentionally not async: it's called both from `push` and when a running job finishes,
114
+ * and must not create a Promise that nobody handles (which would cause UnhandledRejection).
115
+ */
116
+ private start<R>(job: PGradualQueueJob<R>, concurrency: number): void {
117
+ const { logger } = this
118
+ const { defer } = job
119
+
120
+ this.inFlight++
121
+ logger.debug(`inFlight++ ${this.inFlight}/${concurrency}, queue ${this.queue.length}`)
122
+
123
+ runSafe(job.fn)
124
+ .then(result => {
125
+ defer.resolve(result)
126
+ })
127
+ .catch((err: Error) => {
128
+ if (this.cfg.errorMode === ErrorMode.SUPPRESS) {
129
+ logger.error(err)
130
+ defer.resolve() // resolve with `void`
131
+ } else {
132
+ defer.reject(err)
133
+ }
134
+ })
135
+ .finally(() => {
136
+ this.inFlight--
137
+ const currentConcurrency = this.getCurrentConcurrency()
138
+ logger.debug(
139
+ `inFlight-- ${this.inFlight}/${currentConcurrency}, queue ${this.queue.length}`,
140
+ )
141
+
142
+ // Start queued jobs up to the current concurrency limit
143
+ // Use while loop since concurrency may have increased during warmup
144
+ while (this.queue.length && this.inFlight < currentConcurrency) {
145
+ this.start(this.queue.shift()!, currentConcurrency)
146
+ }
147
+ })
140
148
  }
141
149
 
142
150
  get queueSize(): number {
@@ -164,6 +172,7 @@ function runSafe<R>(fn: AsyncFunction<R>): Promise<R> {
164
172
  }
165
173
  }
166
174
 
167
- interface AsyncFunctionWithDefer<R = unknown> extends AsyncFunction<R> {
175
+ interface PGradualQueueJob<R> {
176
+ fn: AsyncFunction<R>
168
177
  defer: DeferredPromise<R>
169
178
  }
@@ -61,67 +61,77 @@ export class PQueue {
61
61
  private readonly logger: CommonLogger
62
62
 
63
63
  inFlight = 0
64
- private queue: AsyncFunction[] = []
64
+ private queue: PQueueJob<any>[] = []
65
65
  private onIdleListeners: DeferredPromise[] = []
66
66
 
67
67
  /**
68
68
  * Push PromiseReturningFunction to the Queue.
69
69
  * Returns a Promise that resolves (or rejects) with the return value from the Promise.
70
70
  */
71
- async push<R>(fn_: AsyncFunction<R>): Promise<R> {
72
- const { concurrency } = this.cfg
73
- const { resolveOnStart, logger } = this
71
+ async push<R>(fn: AsyncFunction<R>): Promise<R> {
72
+ const job: PQueueJob<R> = { fn, defer: pDefer<R>() }
74
73
 
75
- const fn = fn_ as AsyncFunctionWithDefer<R>
76
- fn.defer ||= pDefer<R>()
77
-
78
- if (this.inFlight < concurrency) {
74
+ if (this.inFlight < this.cfg.concurrency) {
79
75
  // There is room for more jobs. Can start immediately
80
- this.inFlight++
81
- logger.debug(`inFlight++ ${this.inFlight}/${concurrency}, queue ${this.queue.length}`)
82
- if (resolveOnStart) fn.defer.resolve()
83
-
84
- runSafe(fn)
85
- .then(result => {
86
- if (!resolveOnStart) fn.defer.resolve(result)
87
- })
88
- .catch((err: Error) => {
89
- if (resolveOnStart) {
90
- logger.error(err)
91
- return
92
- }
93
-
94
- if (this.cfg.errorMode === ErrorMode.SUPPRESS) {
95
- logger.error(err)
96
- fn.defer.resolve() // resolve with `void`
97
- } else {
98
- // Should be handled on the outside, otherwise it'll cause UnhandledRejection
99
- // Not logging, because it's re-thrown upstream
100
- fn.defer.reject(err)
101
- }
102
- })
103
- .finally(() => {
104
- this.inFlight--
105
- logger.debug(`inFlight-- ${this.inFlight}/${concurrency}, queue ${this.queue.length}`)
106
-
107
- // check if there's room to start next job
108
- if (this.queue.length && this.inFlight <= concurrency) {
109
- const nextFn = this.queue.shift()!
110
- void this.push(nextFn)
111
- } else {
112
- if (this.inFlight === 0) {
113
- logger.debug('onIdle')
114
- this.onIdleListeners.forEach(defer => defer.resolve())
115
- this.onIdleListeners.length = 0 // empty the array
116
- }
117
- }
118
- })
76
+ this.start(job)
119
77
  } else {
120
- this.queue.push(fn)
121
- logger.debug(`inFlight ${this.inFlight}/${concurrency}, queue++ ${this.queue.length}`)
78
+ this.queue.push(job)
79
+ this.logger.debug(
80
+ `inFlight ${this.inFlight}/${this.cfg.concurrency}, queue++ ${this.queue.length}`,
81
+ )
122
82
  }
123
83
 
124
- return await fn.defer
84
+ return await job.defer
85
+ }
86
+
87
+ /**
88
+ * Starts the job and settles its `defer`.
89
+ * Intentionally not async: it's called both from `push` and when a running job finishes,
90
+ * and must not create a Promise that nobody handles (which would cause UnhandledRejection).
91
+ */
92
+ private start<R>(job: PQueueJob<R>): void {
93
+ const { concurrency } = this.cfg
94
+ const { resolveOnStart, logger } = this
95
+ const { defer } = job
96
+
97
+ this.inFlight++
98
+ logger.debug(`inFlight++ ${this.inFlight}/${concurrency}, queue ${this.queue.length}`)
99
+ if (resolveOnStart) defer.resolve()
100
+
101
+ runSafe(job.fn)
102
+ .then(result => {
103
+ if (!resolveOnStart) defer.resolve(result)
104
+ })
105
+ .catch((err: Error) => {
106
+ if (resolveOnStart) {
107
+ logger.error(err)
108
+ return
109
+ }
110
+
111
+ if (this.cfg.errorMode === ErrorMode.SUPPRESS) {
112
+ logger.error(err)
113
+ defer.resolve() // resolve with `void`
114
+ } else {
115
+ // Should be handled on the outside, otherwise it'll cause UnhandledRejection
116
+ // Not logging, because it's re-thrown upstream
117
+ defer.reject(err)
118
+ }
119
+ })
120
+ .finally(() => {
121
+ this.inFlight--
122
+ logger.debug(`inFlight-- ${this.inFlight}/${concurrency}, queue ${this.queue.length}`)
123
+
124
+ // check if there's room to start next job
125
+ if (this.queue.length && this.inFlight < concurrency) {
126
+ this.start(this.queue.shift()!)
127
+ } else {
128
+ if (this.inFlight === 0) {
129
+ logger.debug('onIdle')
130
+ this.onIdleListeners.forEach(defer => defer.resolve())
131
+ this.onIdleListeners.length = 0 // empty the array
132
+ }
133
+ }
134
+ })
125
135
  }
126
136
 
127
137
  get queueSize(): number {
@@ -155,6 +165,7 @@ function runSafe<R>(fn: AsyncFunction<R>): Promise<R> {
155
165
  }
156
166
  }
157
167
 
158
- interface AsyncFunctionWithDefer<R> extends AsyncFunction<R> {
168
+ interface PQueueJob<R> {
169
+ fn: AsyncFunction<R>
159
170
  defer: DeferredPromise<R>
160
171
  }