aveazul 1.0.0 → 1.0.1
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/README.md +10 -1
- package/lib/any.js +55 -0
- package/lib/aveazul.js +40 -43
- package/lib/not-implemented.js +0 -1
- package/lib/operational-error.js +46 -0
- package/lib/promisify-all.js +1 -1
- package/lib/promisify.js +2 -2
- package/lib/using.js +29 -15
- package/lib/util.js +18 -0
- package/package.json +10 -4
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# AveAzul
|
|
1
|
+
# AveAzul.js
|
|
2
2
|
|
|
3
3
|
AveAzul ("Blue Bird" in Spanish) serves as a near drop-in replacement for Bluebird. It's built on native Promise, by extending it with the familiar utility methods from Bluebird.
|
|
4
4
|
|
|
@@ -14,6 +14,10 @@ Further, if you like Bluebird's API but want to use native Promises, AveAzul giv
|
|
|
14
14
|
- Implements most commonly used Bluebird methods
|
|
15
15
|
- Comprehensive test suite ensuring compatibility
|
|
16
16
|
|
|
17
|
+
## Requirements
|
|
18
|
+
|
|
19
|
+
- node.js version >= 12
|
|
20
|
+
|
|
17
21
|
## Installation
|
|
18
22
|
|
|
19
23
|
```bash
|
|
@@ -143,6 +147,7 @@ Key differences to be aware of:
|
|
|
143
147
|
- `all()` - Like Promise.all(), resolves when all promises resolve, rejects if any reject
|
|
144
148
|
- `call(propertyName, ...args)` - Call a method on the resolved value with the provided arguments
|
|
145
149
|
- `asCallback(callback, options?)` - Register a Node-style callback that handles the resolution or rejection
|
|
150
|
+
- `error(handler)` - Like catch(), but only catches operational errors, letting programmer errors bubble up
|
|
146
151
|
|
|
147
152
|
### Static Methods
|
|
148
153
|
|
|
@@ -164,6 +169,10 @@ Key differences to be aware of:
|
|
|
164
169
|
- `using(resources, fn)` - Manage resources with automatic cleanup
|
|
165
170
|
- `join(...values, handler?)` - Wait for multiple promises and pass their resolved values as separate arguments to the handler function. If no handler is provided, behaves like Promise.all
|
|
166
171
|
|
|
172
|
+
### Error Types
|
|
173
|
+
|
|
174
|
+
- `AveAzul.OperationalError` - Error type for representing expected operational errors (network failures, validation errors, etc.)
|
|
175
|
+
|
|
167
176
|
### PromisifyAll Options
|
|
168
177
|
|
|
169
178
|
- `suffix` (default: 'Async') - Suffix to append to promisified method names
|
package/lib/any.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { toArray, isPromise } = require("./util");
|
|
4
|
+
const { AggregateError } = require("@jchip/error");
|
|
5
|
+
|
|
6
|
+
function addStaticAny(AveAzul, force = false) {
|
|
7
|
+
if (force || !AveAzul.any) {
|
|
8
|
+
AveAzul.any = function (args) {
|
|
9
|
+
try {
|
|
10
|
+
args = toArray(args);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
return AveAzul.reject(error);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (args.length === 0) {
|
|
16
|
+
return AveAzul.reject(
|
|
17
|
+
new RangeError(
|
|
18
|
+
"Input array must contain at least 1 items but contains only 0 items"
|
|
19
|
+
)
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return new AveAzul((resolve, reject) => {
|
|
24
|
+
const len = args.length;
|
|
25
|
+
let settled = false;
|
|
26
|
+
const errors = [];
|
|
27
|
+
|
|
28
|
+
const doFinish = (value) => {
|
|
29
|
+
if (settled) return;
|
|
30
|
+
settled = true;
|
|
31
|
+
resolve(value);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const addError = (err) => {
|
|
35
|
+
errors.push(err);
|
|
36
|
+
if (!settled && errors.length >= len) {
|
|
37
|
+
settled = true;
|
|
38
|
+
reject(new AggregateError(errors));
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
for (let i = 0; i < len; i++) {
|
|
43
|
+
const arg = args[i];
|
|
44
|
+
if (isPromise(arg)) {
|
|
45
|
+
arg.then(doFinish, addError);
|
|
46
|
+
} else {
|
|
47
|
+
doFinish(arg);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports.addStaticAny = addStaticAny;
|
package/lib/aveazul.js
CHANGED
|
@@ -5,8 +5,9 @@ const { promisify } = require("./promisify");
|
|
|
5
5
|
const { promisifyAll } = require("./promisify-all");
|
|
6
6
|
const { Disposer } = require("./disposer");
|
|
7
7
|
const { using } = require("./using");
|
|
8
|
-
const { isPromise, triggerUncaughtException } = require("./util");
|
|
9
|
-
|
|
8
|
+
const { isPromise, triggerUncaughtException, toArray } = require("./util");
|
|
9
|
+
const { AggregateError } = require("@jchip/error");
|
|
10
|
+
const { OperationalError, isOperationalError } = require("./operational-error");
|
|
10
11
|
/**
|
|
11
12
|
* @fileoverview
|
|
12
13
|
* AveAzul ("Blue Bird" in Spanish) - Extended Promise class that provides Bluebird like utility methods
|
|
@@ -86,20 +87,7 @@ class AveAzul extends Promise {
|
|
|
86
87
|
*/
|
|
87
88
|
any() {
|
|
88
89
|
return this.then((args) => {
|
|
89
|
-
|
|
90
|
-
// Check if args is iterable
|
|
91
|
-
if (args != null && typeof args[Symbol.iterator] === "function") {
|
|
92
|
-
// Convert iterable to array, must do this to get the length, in order
|
|
93
|
-
// to detect if too many errors occurred and completion is impossible.
|
|
94
|
-
args = Array.from(args);
|
|
95
|
-
} else {
|
|
96
|
-
throw new TypeError(
|
|
97
|
-
"expecting an array or an iterable object but got " + args
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return AveAzul.any(args);
|
|
90
|
+
return AveAzul.any(toArray(args));
|
|
103
91
|
});
|
|
104
92
|
}
|
|
105
93
|
|
|
@@ -140,7 +128,12 @@ class AveAzul extends Promise {
|
|
|
140
128
|
* @returns {Promise} Promise that rejects if timeout occurs
|
|
141
129
|
*/
|
|
142
130
|
timeout(ms, message = "operation timed out") {
|
|
143
|
-
return
|
|
131
|
+
return xaa
|
|
132
|
+
.timeout(ms, message, {
|
|
133
|
+
Promise: AveAzul,
|
|
134
|
+
TimeoutError: OperationalError,
|
|
135
|
+
})
|
|
136
|
+
.run(this);
|
|
144
137
|
}
|
|
145
138
|
|
|
146
139
|
/**
|
|
@@ -291,18 +284,7 @@ class AveAzul extends Promise {
|
|
|
291
284
|
|
|
292
285
|
some(count) {
|
|
293
286
|
return this.then((args) => {
|
|
294
|
-
|
|
295
|
-
// Check if args is iterable
|
|
296
|
-
if (args != null && typeof args[Symbol.iterator] === "function") {
|
|
297
|
-
// Convert iterable to array, must do this to get the length, in order
|
|
298
|
-
// to detect if too many errors occurred and completion is impossible.
|
|
299
|
-
args = Array.from(args);
|
|
300
|
-
} else {
|
|
301
|
-
throw new TypeError(
|
|
302
|
-
"expecting an array or an iterable object but got " + args
|
|
303
|
-
);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
287
|
+
args = toArray(args);
|
|
306
288
|
|
|
307
289
|
return new AveAzul((resolve, reject) => {
|
|
308
290
|
// If too many promises are rejected so that the promise can never become fulfilled,
|
|
@@ -314,17 +296,23 @@ class AveAzul extends Promise {
|
|
|
314
296
|
const results = [];
|
|
315
297
|
const len = args.length;
|
|
316
298
|
|
|
299
|
+
let settled = false;
|
|
300
|
+
|
|
317
301
|
const addDone = (result) => {
|
|
302
|
+
if (settled) return;
|
|
318
303
|
results.push(result);
|
|
319
304
|
if (results.length >= count) {
|
|
305
|
+
settled = true;
|
|
320
306
|
// Resolve with exactly count results to match Bluebird's behavior
|
|
321
307
|
resolve(results.slice(0, count));
|
|
322
308
|
}
|
|
323
309
|
};
|
|
324
310
|
|
|
325
311
|
const addError = (err) => {
|
|
312
|
+
if (settled) return;
|
|
326
313
|
errors.push(err);
|
|
327
314
|
if (len - errors.length < count) {
|
|
315
|
+
settled = true;
|
|
328
316
|
reject(new AggregateError(errors, `aggregate error`));
|
|
329
317
|
}
|
|
330
318
|
};
|
|
@@ -348,19 +336,7 @@ class AveAzul extends Promise {
|
|
|
348
336
|
*/
|
|
349
337
|
all() {
|
|
350
338
|
return this.then((value) => {
|
|
351
|
-
|
|
352
|
-
// Check if value is iterable
|
|
353
|
-
if (value != null && typeof value[Symbol.iterator] === "function") {
|
|
354
|
-
// Convert iterable to array
|
|
355
|
-
value = Array.from(value);
|
|
356
|
-
} else {
|
|
357
|
-
throw new TypeError(
|
|
358
|
-
"expecting an array or an iterable object but got " + value
|
|
359
|
-
);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
return AveAzul.all(value);
|
|
339
|
+
return AveAzul.all(toArray(value));
|
|
364
340
|
});
|
|
365
341
|
}
|
|
366
342
|
|
|
@@ -420,6 +396,21 @@ class AveAzul extends Promise {
|
|
|
420
396
|
return obj[methodName].call(obj, ...args);
|
|
421
397
|
});
|
|
422
398
|
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Catches only operational errors and passes them to the handler.
|
|
402
|
+
* Programmer errors (non-operational) are rethrown.
|
|
403
|
+
* @param {Function} handler - Function to handle operational errors
|
|
404
|
+
* @returns {Promise} - Promise with the error handled or rethrown
|
|
405
|
+
*/
|
|
406
|
+
error(handler) {
|
|
407
|
+
return this.catch((err) => {
|
|
408
|
+
if (isOperationalError(err)) {
|
|
409
|
+
return handler(err);
|
|
410
|
+
}
|
|
411
|
+
throw err;
|
|
412
|
+
});
|
|
413
|
+
}
|
|
423
414
|
}
|
|
424
415
|
|
|
425
416
|
/**
|
|
@@ -584,7 +575,7 @@ AveAzul.using = (resources, ...args) => {
|
|
|
584
575
|
* @returns {Promise} Promise that resolves with the handler's return value
|
|
585
576
|
*/
|
|
586
577
|
AveAzul.join = function (...args) {
|
|
587
|
-
if (args.length > 1 && typeof args.
|
|
578
|
+
if (args.length > 1 && typeof args[args.length - 1] === "function") {
|
|
588
579
|
const handler = args.pop();
|
|
589
580
|
return AveAzul.all(args).then((results) => handler(...results));
|
|
590
581
|
} else {
|
|
@@ -641,8 +632,14 @@ AveAzul.some = function (promises, count) {
|
|
|
641
632
|
return AveAzul.resolve(promises).some(count);
|
|
642
633
|
};
|
|
643
634
|
|
|
635
|
+
const { addStaticAny } = require("./any");
|
|
636
|
+
addStaticAny(AveAzul);
|
|
637
|
+
|
|
644
638
|
// Setup the not implemented methods
|
|
645
639
|
const { setupNotImplemented } = require("./not-implemented");
|
|
646
640
|
setupNotImplemented(AveAzul);
|
|
647
641
|
|
|
642
|
+
// Add these static properties after the class definition
|
|
643
|
+
AveAzul.OperationalError = OperationalError;
|
|
644
|
+
|
|
648
645
|
module.exports = AveAzul;
|
package/lib/not-implemented.js
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* OperationalError class for representing errors that are expected during normal operation
|
|
5
|
+
* Similar to Bluebird's OperationalError
|
|
6
|
+
*/
|
|
7
|
+
class OperationalError extends Error {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "OperationalError";
|
|
11
|
+
this.isOperational = true;
|
|
12
|
+
|
|
13
|
+
// Capture stack trace
|
|
14
|
+
if (Error.captureStackTrace) {
|
|
15
|
+
Error.captureStackTrace(this, this.constructor);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Check if an error is an operational error
|
|
22
|
+
* @param {*} error - Error to check
|
|
23
|
+
* @returns {boolean} True if the error is operational
|
|
24
|
+
*/
|
|
25
|
+
function isOperationalError(error) {
|
|
26
|
+
if (!error || typeof error !== "object") return false;
|
|
27
|
+
return error instanceof OperationalError || error.isOperational === true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Check if an error is a programmer error (unexpected, likely a bug)
|
|
32
|
+
* @param {*} error - Error to check
|
|
33
|
+
* @returns {boolean} True if the error is a programmer error
|
|
34
|
+
*/
|
|
35
|
+
function isProgrammerError(error) {
|
|
36
|
+
if (!error || typeof error !== "object") return false;
|
|
37
|
+
return !isOperationalError(error);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Only export the OperationalError class
|
|
41
|
+
module.exports = {
|
|
42
|
+
OperationalError,
|
|
43
|
+
// Internal utilities used by AveAzul.prototype.error
|
|
44
|
+
isOperationalError,
|
|
45
|
+
isProgrammerError,
|
|
46
|
+
};
|
package/lib/promisify-all.js
CHANGED
|
@@ -76,7 +76,7 @@ function promisifyAll2(obj, options) {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
obj[promisifiedKey] = options.promisifier(value, defaultPromisifier, {
|
|
79
|
-
context: obj,
|
|
79
|
+
// context: obj, // promisified function should get the binded object using this
|
|
80
80
|
copyProps: false,
|
|
81
81
|
multiArgs: options.multiArgs,
|
|
82
82
|
Promise: options.Promise,
|
package/lib/promisify.js
CHANGED
|
@@ -22,7 +22,7 @@ module.exports.promisify = function promisify(fn, _options) {
|
|
|
22
22
|
const Promise = options.Promise;
|
|
23
23
|
const multiArgs = !!options.multiArgs;
|
|
24
24
|
|
|
25
|
-
const promisifiedFn = (...args)
|
|
25
|
+
const promisifiedFn = function (...args) {
|
|
26
26
|
return new Promise((resolve, reject) => {
|
|
27
27
|
// add a callback to the end of the arguments to transfer the result to the promise
|
|
28
28
|
args.push((err, ...values) => {
|
|
@@ -37,7 +37,7 @@ module.exports.promisify = function promisify(fn, _options) {
|
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
// call the original function with the updated args
|
|
40
|
-
fn.call(options.context, ...args);
|
|
40
|
+
fn.call(options.context || this, ...args);
|
|
41
41
|
});
|
|
42
42
|
};
|
|
43
43
|
|
package/lib/using.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { Disposer } = require("./disposer");
|
|
4
4
|
const { isPromise } = require("./util");
|
|
5
|
+
const { AggregateError } = require("@jchip/error");
|
|
5
6
|
|
|
6
7
|
const SYM_FN_DISPOSE = Symbol("fnDispose");
|
|
7
8
|
/**
|
|
@@ -28,6 +29,25 @@ function using(resources, handler, Promise, asArray) {
|
|
|
28
29
|
// Expect Promise to be AveAzul or Bluebird that has map method
|
|
29
30
|
const acquisitionErrors = [];
|
|
30
31
|
|
|
32
|
+
// Helper function to process a disposer
|
|
33
|
+
const processDisposer = async (resource, disposer) => {
|
|
34
|
+
try {
|
|
35
|
+
const res = await disposer._promise;
|
|
36
|
+
resource._result = res;
|
|
37
|
+
resource[SYM_FN_DISPOSE] = disposer._data;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
acquisitionErrors.push(error);
|
|
40
|
+
resource._error = error;
|
|
41
|
+
}
|
|
42
|
+
return resource;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Helper to check if something is a disposer
|
|
46
|
+
const isDisposer = (obj) =>
|
|
47
|
+
obj &&
|
|
48
|
+
(obj instanceof Disposer ||
|
|
49
|
+
(obj._promise && typeof obj._data === "function"));
|
|
50
|
+
|
|
31
51
|
const acquireResources = () => {
|
|
32
52
|
const promiseRes = resources.map((resource) => {
|
|
33
53
|
// if it's a promise-like, wait for its resolved value
|
|
@@ -38,27 +58,21 @@ function using(resources, handler, Promise, asArray) {
|
|
|
38
58
|
});
|
|
39
59
|
|
|
40
60
|
return Promise.map(promiseRes, async (resource) => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
(resource
|
|
44
|
-
(resource._promise && typeof resource._data === "function"))
|
|
45
|
-
) {
|
|
46
|
-
try {
|
|
47
|
-
const res = await resource._promise;
|
|
48
|
-
resource._result = res;
|
|
49
|
-
resource[SYM_FN_DISPOSE] = resource._data;
|
|
50
|
-
} catch (error) {
|
|
51
|
-
acquisitionErrors.push(error);
|
|
52
|
-
resource._error = error;
|
|
53
|
-
}
|
|
54
|
-
return resource;
|
|
61
|
+
// If it's directly a disposer
|
|
62
|
+
if (isDisposer(resource)) {
|
|
63
|
+
return processDisposer(resource, resource);
|
|
55
64
|
}
|
|
56
65
|
|
|
57
66
|
// if it's a promise like, wait for its resolved value
|
|
58
67
|
if (resource && resource.___promise) {
|
|
59
68
|
try {
|
|
60
69
|
const res = await resource.___promise;
|
|
61
|
-
|
|
70
|
+
// Check if the resolved value is a disposer
|
|
71
|
+
if (isDisposer(res)) {
|
|
72
|
+
return processDisposer(resource, res);
|
|
73
|
+
} else {
|
|
74
|
+
resource._result = res;
|
|
75
|
+
}
|
|
62
76
|
} catch (error) {
|
|
63
77
|
acquisitionErrors.push(error);
|
|
64
78
|
resource._error = error;
|
package/lib/util.js
CHANGED
|
@@ -183,6 +183,23 @@ function triggerUncaughtException(error) {
|
|
|
183
183
|
}, 0);
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
function toArray(args) {
|
|
187
|
+
if (!Array.isArray(args)) {
|
|
188
|
+
// Check if args is iterable
|
|
189
|
+
if (args != null && typeof args[Symbol.iterator] === "function") {
|
|
190
|
+
// Convert iterable to array, must do this to get the length, in order
|
|
191
|
+
// to detect if too many errors occurred and completion is impossible.
|
|
192
|
+
args = Array.from(args);
|
|
193
|
+
} else {
|
|
194
|
+
throw new TypeError(
|
|
195
|
+
"expecting an array or an iterable object but got " + args
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return args;
|
|
201
|
+
}
|
|
202
|
+
|
|
186
203
|
module.exports.copyOwnProperties = copyOwnProperties;
|
|
187
204
|
module.exports.isClass = isClass;
|
|
188
205
|
module.exports.isIdentifier = isIdentifier;
|
|
@@ -192,3 +209,4 @@ module.exports.isPromise = isPromise;
|
|
|
192
209
|
module.exports.triggerUncaughtException = triggerUncaughtException;
|
|
193
210
|
module.exports.getObjectKeys = getObjectKeys;
|
|
194
211
|
module.exports.isExcludedPrototype = isExcludedPrototype;
|
|
212
|
+
module.exports.toArray = toArray;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aveazul",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Bluebird drop-in replacement built on native Promise",
|
|
5
5
|
"main": "lib/aveazul.js",
|
|
6
6
|
"homepage": "https://github.com/jchip/aveazul",
|
|
@@ -53,18 +53,24 @@
|
|
|
53
53
|
"any",
|
|
54
54
|
"props",
|
|
55
55
|
"filter",
|
|
56
|
-
"reduce"
|
|
56
|
+
"reduce",
|
|
57
|
+
"aveazul",
|
|
58
|
+
"aveazul.js"
|
|
57
59
|
],
|
|
58
60
|
"repository": {
|
|
59
61
|
"type": "git",
|
|
60
62
|
"url": "git+https://github.com/jchip/aveazul.git"
|
|
61
63
|
},
|
|
62
64
|
"dependencies": {
|
|
63
|
-
"
|
|
65
|
+
"@jchip/error": "^1.0.3",
|
|
66
|
+
"xaa": "^1.8.0"
|
|
64
67
|
},
|
|
65
68
|
"devDependencies": {
|
|
66
69
|
"bluebird": "^3.7.2",
|
|
67
|
-
"jest": "^
|
|
70
|
+
"jest": "^28.0.0",
|
|
68
71
|
"rimraf": "^3.0.1"
|
|
72
|
+
},
|
|
73
|
+
"engines": {
|
|
74
|
+
"node": ">=12.0.0"
|
|
69
75
|
}
|
|
70
76
|
}
|