aveazul 0.1.4 → 1.0.0

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 CHANGED
@@ -1,10 +1,18 @@
1
1
  # AveAzul
2
2
 
3
- AveAzul ("Blue Bird" in Spanish) is a Promise library that extends native Promises with Bluebird-like utility methods. Built on top of native Promises, it provides a familiar API for Node.js developers who are used to working with [Bluebird](https://github.com/petkaantonov/bluebird) ([npm](https://www.npmjs.com/package/bluebird)).
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
 
5
- This library helps migrate legacy code that uses Bluebird specific APIs to native Promises with minimal changes. It aims to provide a drop-in replacement that maintains compatibility while leveraging native Promises.
5
+ ## Purpose
6
6
 
7
- Do you prefer Bluebird's API with native Promises? AveAzul gives you both - familiar Bluebird methods built on native Promise.
7
+ The primary goal is to help migrate legacy code, that uses Bluebird APIs extensively, to native Promises. While not a 100% drop-in replacement (some aspects of Bluebird simply can't be replicated), it offers a practical migration path with minimal changes for most cases.
8
+
9
+ Further, if you like Bluebird's API but want to use native Promises, AveAzul gives you both - familiar Bluebird methods built on native Promise.
10
+
11
+ ## Features
12
+
13
+ - Built on native Promises
14
+ - Implements most commonly used Bluebird methods
15
+ - Comprehensive test suite ensuring compatibility
8
16
 
9
17
  ## Installation
10
18
 
@@ -27,6 +35,26 @@ AveAzul.resolve([1, 2, 3])
27
35
  .filter((x) => x > 2)
28
36
  .then((result) => console.log(result)); // [4, 6]
29
37
 
38
+ // Wait for at least 2 promises to be fulfilled
39
+ const fetchUrls = [
40
+ fetch("https://api.example.com/data1"),
41
+ fetch("https://api.example.com/data2"),
42
+ fetch("https://api.example.com/data3"),
43
+ fetch("https://api.example.com/data4"),
44
+ ];
45
+ AveAzul.some(fetchUrls, 2).then((results) =>
46
+ console.log(`Got the first 2 successful results`)
47
+ );
48
+
49
+ // Process items sequentially with mapSeries
50
+ AveAzul.resolve([1, 2, 3])
51
+ .mapSeries(async (x) => {
52
+ // Each item is processed only after the previous one completes
53
+ await new Promise((resolve) => setTimeout(resolve, 100));
54
+ return x * 2;
55
+ })
56
+ .then((result) => console.log(result)); // [2, 4, 6]
57
+
30
58
  // Promisify callback-style functions
31
59
  const fs = require("fs");
32
60
  const readFile = AveAzul.promisify(fs.readFile);
@@ -58,8 +86,37 @@ AveAzul.using(getResource(), (resource) => {
58
86
  console.log(result); // "operation completed"
59
87
  // Resource is automatically closed here, even if an error occurred
60
88
  });
89
+
90
+ // Using spread to apply array results as arguments
91
+ AveAzul.all([getUser(1), getPosts(1), getComments(1)]).spread(
92
+ (user, posts, comments) => {
93
+ // Instead of using .then(([user, posts, comments]) => {...})
94
+ console.log(
95
+ `User ${user.name} has ${posts.length} posts and ${comments.length} comments`
96
+ );
97
+ return { user, activity: { posts, comments } };
98
+ }
99
+ );
61
100
  ```
62
101
 
102
+ ## Migration from Bluebird
103
+
104
+ For most applications, migrating from Bluebird to AveAzul should be as simple as:
105
+
106
+ ```javascript
107
+ // From
108
+ const Promise = require("bluebird");
109
+
110
+ // To
111
+ const Promise = require("aveazul");
112
+ ```
113
+
114
+ Key differences to be aware of:
115
+
116
+ - Some advanced debugging features are not available
117
+ - Performance characteristics may differ
118
+ - A few very specialized methods not available
119
+
63
120
  ## API
64
121
 
65
122
  ### Instance Methods
@@ -67,33 +124,45 @@ AveAzul.using(getResource(), (resource) => {
67
124
  - `tap(fn)` - Execute side effects and return original value
68
125
  - `filter(fn)` - Filter array elements
69
126
  - `map(fn)` - Transform array elements
127
+ - `mapSeries(fn)` - Transform array elements sequentially
70
128
  - `return(value)` - Inject a new value
71
129
  - `each(fn)` - Iterate over array elements
72
130
  - `delay(ms)` - Delay resolution
73
131
  - `timeout(ms, message?)` - Reject after specified time
74
132
  - `props(obj)` - Resolve object properties
133
+ - `spread(fn)` - Apply array values as arguments to function
75
134
  - `tapCatch(fn)` - Execute side effects on rejection
76
135
  - `reduce(fn, initialValue?)` - Reduce array elements
136
+ - `some(count)` - Resolves when a specified number of promises in the array have resolved
77
137
  - `throw(reason)` - Return rejected promise
78
138
  - `catchThrow(reason)` - Catch and throw new error
79
139
  - `catchReturn(value)` - Catch and return value
80
140
  - `get(propertyPath)` - Retrieve property value
81
141
  - `disposer(fn)` - Create a disposer for use with AveAzul.using() for resource cleanup
142
+ - `any()` - Resolves when any promise in the iterable resolves, rejecting if all reject
143
+ - `all()` - Like Promise.all(), resolves when all promises resolve, rejects if any reject
144
+ - `call(propertyName, ...args)` - Call a method on the resolved value with the provided arguments
145
+ - `asCallback(callback, options?)` - Register a Node-style callback that handles the resolution or rejection
82
146
 
83
147
  ### Static Methods
84
148
 
85
149
  - `delay(ms, value?)` - Resolve after specified time
86
150
  - `map(value, fn)` - Transform array elements
151
+ - `mapSeries(value, fn)` - Transform array elements one at a time in sequence
87
152
  - `try(fn)` - Wrap sync/async functions
88
153
  - `props(obj)` - Resolve object properties
89
154
  - `defer()` - Create a deferred promise
90
155
  - `promisify(fn, options?)` - Convert callback-style functions to promises (preserves original function properties)
156
+ - `fromNode(fn, options?)` - Convert Node-style callback functions to promise-returning functions
157
+ - `fromCallback(fn, options?)` - Alias for fromNode
91
158
  - `each(items, fn)` - Iterate over array elements
92
159
  - `reduce(array, fn, initialValue?)` - Reduce array elements
160
+ - `some(promises, count)` - Wait for a specified number of promises to be fulfilled
93
161
  - `method(fn)` - Creates a method that returns a promise resolving to the value returned by the original function
94
162
  - `throw(reason)` - Return rejected promise
95
163
  - `promisifyAll(target, options?)` - Convert all methods of an object/class to promises
96
164
  - `using(resources, fn)` - Manage resources with automatic cleanup
165
+ - `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
97
166
 
98
167
  ### PromisifyAll Options
99
168
 
@@ -112,12 +181,19 @@ npm install
112
181
  npm test
113
182
  npm run test:watch
114
183
  npm run test:coverage
184
+
185
+ # Test against Bluebird for compatibility
186
+ npm run jest:bluebird -- test/[name].test.js
115
187
  ```
116
188
 
189
+ ## Contributing
190
+
191
+ Contributions are welcome! Please feel free to submit a Pull Request.
192
+
117
193
  ## License
118
194
 
119
195
  Apache-2.0
120
196
 
121
197
  ## Author
122
198
 
123
- Joel Chen
199
+ Joel Chen, with assistant from Cursor Claude-3.7-sonnet
package/lib/aveazul.js CHANGED
@@ -6,8 +6,10 @@ const { promisifyAll } = require("./promisify-all");
6
6
  const { Disposer } = require("./disposer");
7
7
  const { using } = require("./using");
8
8
  const { isPromise, triggerUncaughtException } = require("./util");
9
+
9
10
  /**
10
- * AveAzul ("Blue Bird" in Spanish) - Extended Promise class that provides Bluebird-like utility methods
11
+ * @fileoverview
12
+ * AveAzul ("Blue Bird" in Spanish) - Extended Promise class that provides Bluebird like utility methods
11
13
  * This implementation is inspired by and provides similar APIs to the Bluebird Promise library,
12
14
  * but built on top of native Promises. The name is a Spanish play on words referencing Bluebird.
13
15
  * @extends Promise
@@ -31,8 +33,8 @@ class AveAzul extends Promise {
31
33
  * @returns {Promise} Promise that resolves with the original value
32
34
  */
33
35
  tap(fn) {
34
- return this.then((value) => {
35
- fn(value);
36
+ return this.then(async (value) => {
37
+ await fn(value);
36
38
  return value;
37
39
  });
38
40
  }
@@ -53,8 +55,18 @@ class AveAzul extends Promise {
53
55
  * @param {Function} fn - Map function to apply to each element
54
56
  * @returns {Promise} Promise that resolves with the mapped array
55
57
  */
56
- map(fn) {
57
- return this.then((value) => xaa.map(value, fn));
58
+ map(fn, options = { concurrency: 50 }) {
59
+ return this.then((value) => xaa.map(value, fn, options));
60
+ }
61
+
62
+ /**
63
+ * Bluebird-style mapSeries() method for array operations
64
+ * Similar to Bluebird's Promise.prototype.mapSeries()
65
+ * @param {Function} fn - Map function to apply to each element
66
+ * @returns {Promise} Promise that resolves with the mapped array
67
+ */
68
+ mapSeries(fn) {
69
+ return this.map(fn, { concurrency: 1 });
58
70
  }
59
71
 
60
72
  /**
@@ -67,6 +79,30 @@ class AveAzul extends Promise {
67
79
  return this.then(() => value);
68
80
  }
69
81
 
82
+ /**
83
+ * Bluebird-style any() method for waiting for any promises to resolve
84
+ * @param {Array|Iterable} promises - Array or iterable of promises
85
+ * @returns {Promise} Promise that resolves with the first resolved promise
86
+ */
87
+ any() {
88
+ return this.then((args) => {
89
+ if (!Array.isArray(args)) {
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);
103
+ });
104
+ }
105
+
70
106
  /**
71
107
  * Bluebird-style each() method for array iteration
72
108
  * Similar to Bluebird's Promise.prototype.each()
@@ -222,8 +258,168 @@ class AveAzul extends Promise {
222
258
  if (typeof fn !== "function") {
223
259
  throw new TypeError("Expected a function");
224
260
  }
261
+
225
262
  return new Disposer(fn, this);
226
263
  }
264
+
265
+ /**
266
+ * Bluebird-style spread() method for handling array arguments
267
+ * Similar to Bluebird's Promise.prototype.spread()
268
+ * @param {Function} fn - Function to apply to the array arguments
269
+ * @returns {Promise} Promise that resolves with the function's return value
270
+ */
271
+ spread(fn) {
272
+ if (typeof fn !== "function") {
273
+ return AveAzul.reject(
274
+ new TypeError("expecting a function but got " + fn)
275
+ );
276
+ }
277
+
278
+ return this.then(async (args) => {
279
+ if (Array.isArray(args)) {
280
+ for (let i = 0; i < args.length; i++) {
281
+ if (isPromise(args[i])) {
282
+ args[i] = await args[i];
283
+ }
284
+ }
285
+ return fn(...args);
286
+ } else {
287
+ return fn(args);
288
+ }
289
+ });
290
+ }
291
+
292
+ some(count) {
293
+ return this.then((args) => {
294
+ if (!Array.isArray(args)) {
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
+ }
306
+
307
+ return new AveAzul((resolve, reject) => {
308
+ // If too many promises are rejected so that the promise can never become fulfilled,
309
+ // it will be immediately rejected with an AggregateError of the rejection reasons
310
+ // in the order they were thrown in.
311
+ const errors = [];
312
+ // The fulfillment value is an array with count values
313
+ // in the order they were fulfilled.
314
+ const results = [];
315
+ const len = args.length;
316
+
317
+ const addDone = (result) => {
318
+ results.push(result);
319
+ if (results.length >= count) {
320
+ // Resolve with exactly count results to match Bluebird's behavior
321
+ resolve(results.slice(0, count));
322
+ }
323
+ };
324
+
325
+ const addError = (err) => {
326
+ errors.push(err);
327
+ if (len - errors.length < count) {
328
+ reject(new AggregateError(errors, `aggregate error`));
329
+ }
330
+ };
331
+
332
+ for (let i = 0; i < len; i++) {
333
+ const x = args[i];
334
+ if (isPromise(x)) {
335
+ x.then(addDone, addError);
336
+ } else {
337
+ addDone(x);
338
+ }
339
+ }
340
+ });
341
+ });
342
+ }
343
+
344
+ /**
345
+ * Bluebird-style all() method for array operations
346
+ * Similar to Promise.all() but operates on the resolved value of this promise
347
+ * @returns {Promise} Promise that resolves when all items in the array resolve
348
+ */
349
+ all() {
350
+ return this.then((value) => {
351
+ if (!Array.isArray(value)) {
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);
364
+ });
365
+ }
366
+
367
+ /**
368
+ * Bluebird-style asCallback() method
369
+ * Attaches a callback to the promise and returns the promise.
370
+ * The callback is invoked when the promise is resolved or rejected.
371
+ *
372
+ * @param {Function} cb - Node.js-style callback function (err, value)
373
+ * @param {Object} [options] - Additional options
374
+ * @param {boolean} [options.spread=false] - Pass array values as arguments to callback
375
+ * @returns {Promise} The same promise instance
376
+ */
377
+ asCallback(cb, options = {}) {
378
+ if (typeof cb !== "function") {
379
+ return this;
380
+ }
381
+
382
+ const spread = options && options.spread === true;
383
+
384
+ this.then(
385
+ (value) => {
386
+ try {
387
+ if (spread && Array.isArray(value)) {
388
+ cb(null, ...value);
389
+ } else {
390
+ cb(null, value);
391
+ }
392
+ } catch (err) {
393
+ AveAzul.___throwUncaughtError(err);
394
+ }
395
+ },
396
+ (reason) => {
397
+ try {
398
+ cb(reason);
399
+ } catch (err) {
400
+ AveAzul.___throwUncaughtError(err);
401
+ }
402
+ }
403
+ );
404
+
405
+ return this;
406
+ }
407
+
408
+ nodeify(cb, options) {
409
+ return this.asCallback(cb, options);
410
+ }
411
+
412
+ /**
413
+ * Bluebird-style call() method for calling a method on the resolved value
414
+ * @param {string} methodName - Name of the method to call
415
+ * @param {...any} args - Arguments to pass to the method
416
+ * @returns {Promise} Promise that resolves with the method's return value
417
+ */
418
+ call(methodName, ...args) {
419
+ return this.then(function (obj) {
420
+ return obj[methodName].call(obj, ...args);
421
+ });
422
+ }
227
423
  }
228
424
 
229
425
  /**
@@ -249,7 +445,16 @@ AveAzul.delay = (ms, value) => {
249
445
  * @param {Function} fn - Map function to apply to each element
250
446
  * @returns {Promise} Promise that resolves with the mapped array
251
447
  */
252
- AveAzul.map = (value, fn) => AveAzul.resolve(xaa.map(value, fn));
448
+ AveAzul.map = (value, fn, options = { concurrency: 50 }) =>
449
+ AveAzul.resolve(value).map(fn, options);
450
+
451
+ /**
452
+ * Bluebird-style mapSeries() for array operations
453
+ * @param {Array} value - Array to map over
454
+ * @param {Function} fn - Map function to apply to each element
455
+ * @returns {Promise} Promise that resolves with the mapped array
456
+ */
457
+ AveAzul.mapSeries = (value, fn) => AveAzul.map(value, fn, { concurrency: 1 });
253
458
 
254
459
  /**
255
460
  * Bluebird-style try() for wrapping sync/async functions
@@ -371,6 +576,52 @@ AveAzul.using = (resources, ...args) => {
371
576
  return using([resources, ...args], handler, AveAzul, false);
372
577
  };
373
578
 
579
+ /**
580
+ * Bluebird-style join() for joining promises
581
+ *
582
+ * @param {...Promise} args - Promises to join
583
+ * @param {Function} handler - Handler function to apply to the joined results
584
+ * @returns {Promise} Promise that resolves with the handler's return value
585
+ */
586
+ AveAzul.join = function (...args) {
587
+ if (args.length > 1 && typeof args.at(-1) === "function") {
588
+ const handler = args.pop();
589
+ return AveAzul.all(args).then((results) => handler(...results));
590
+ } else {
591
+ return AveAzul.all(args);
592
+ }
593
+ };
594
+
595
+ function fromCallback(fn, options) {
596
+ return new AveAzul((resolve, reject) => {
597
+ try {
598
+ fn((err, ...args) => {
599
+ if (err) {
600
+ reject(err);
601
+ } else {
602
+ if (options && options.multiArgs) {
603
+ resolve(args);
604
+ } else {
605
+ resolve(args[0]);
606
+ }
607
+ }
608
+ });
609
+ } catch (err) {
610
+ reject(err);
611
+ }
612
+ });
613
+ }
614
+
615
+ AveAzul.fromNode = fromCallback;
616
+
617
+ /**
618
+ * Bluebird-style fromCallback() for converting callback-based functions to promises
619
+ * @param {Function} fn - Function to convert
620
+ * @param {Object} [options] - Options object
621
+ * @returns {Promise} Promise that resolves with the function's return value
622
+ */
623
+ AveAzul.fromCallback = fromCallback;
624
+
374
625
  /**
375
626
  * @description
376
627
  * When fatal error and AveAzul needs to crash the process,
@@ -380,4 +631,18 @@ AveAzul.using = (resources, ...args) => {
380
631
  */
381
632
  AveAzul.___throwUncaughtError = triggerUncaughtException;
382
633
 
634
+ /**
635
+ * Bluebird-style some() for waiting for some promises to resolve
636
+ * @param {Array|Iterable} promises - Array or iterable of promises
637
+ * @param {number} count - Number of promises that need to resolve
638
+ * @returns {Promise} Promise that resolves when count promises have resolved
639
+ */
640
+ AveAzul.some = function (promises, count) {
641
+ return AveAzul.resolve(promises).some(count);
642
+ };
643
+
644
+ // Setup the not implemented methods
645
+ const { setupNotImplemented } = require("./not-implemented");
646
+ setupNotImplemented(AveAzul);
647
+
383
648
  module.exports = AveAzul;
package/lib/disposer.js CHANGED
@@ -5,8 +5,8 @@
5
5
  * @private
6
6
  */
7
7
  class Disposer {
8
- constructor(data, promise) {
9
- this._data = data; // The cleanup function
8
+ constructor(fn, promise) {
9
+ this._data = fn; // The cleanup function
10
10
  this._promise = promise; // The promise that resolves to the resource
11
11
  }
12
12
  }
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+
3
+ function createNotImplemented(name) {
4
+ return function () {
5
+ const msg = name + " Not implemented in aveazul";
6
+ console.error(msg);
7
+ throw new Error(msg);
8
+ };
9
+ }
10
+
11
+ function createInstanceNotImplemented(AveAzul) {
12
+ const methods = [
13
+ "then",
14
+ "spread",
15
+ "catch",
16
+ "error",
17
+ "finally",
18
+ "bind",
19
+ "isFulfilled",
20
+ "isRejected",
21
+ "isPending",
22
+ "isCancelled",
23
+ "value",
24
+ "reason",
25
+ "all",
26
+ "props",
27
+ "any",
28
+ "some",
29
+ "map",
30
+ "reduce",
31
+ "filter",
32
+ "each",
33
+ "mapSeries",
34
+ "disposer",
35
+ "asCallback",
36
+ "delay",
37
+ "timeout",
38
+ "cancel",
39
+ "tap",
40
+ "tapCatch",
41
+ "call",
42
+ "get",
43
+ "return",
44
+ "throw",
45
+ "catchReturn",
46
+ "catchThrow",
47
+ "reflect",
48
+ "suppressUnhandledRejections",
49
+ "done",
50
+ ];
51
+
52
+ const proto = AveAzul.prototype;
53
+ const ret = [];
54
+ for (const method of methods) {
55
+ if (!proto[method]) {
56
+ ret.push(method);
57
+ proto[method] = createNotImplemented("instance " + method);
58
+ }
59
+ }
60
+ return ret;
61
+ }
62
+
63
+ function createStaticNotImplemented(AveAzul) {
64
+ const methods = [
65
+ "join",
66
+ "try",
67
+ "method",
68
+ "resolve",
69
+ "reject",
70
+ "props",
71
+ "any",
72
+ "some",
73
+ "map",
74
+ "reduce",
75
+ "filter",
76
+ "each",
77
+ "mapSeries",
78
+ "race",
79
+ "using",
80
+ "promisify",
81
+ "promisifyAll",
82
+ "fromCallback",
83
+ "delay",
84
+ "coroutine",
85
+ "coroutine.addYieldHandler",
86
+ "getNewLibraryCopy",
87
+ "noConflict",
88
+ "setScheduler",
89
+ ];
90
+ const ret = [];
91
+ for (const method of methods) {
92
+ if (!AveAzul[method]) {
93
+ ret.push(method);
94
+ AveAzul[method] = createNotImplemented("static " + method);
95
+ }
96
+ }
97
+ return ret;
98
+ }
99
+
100
+ function setupNotImplemented(AveAzul) {
101
+ const instanceMethods = createInstanceNotImplemented(AveAzul);
102
+ const staticMethods = createStaticNotImplemented(AveAzul);
103
+ AveAzul.__notImplementedInstance = instanceMethods;
104
+ AveAzul.__notImplementedStatic = staticMethods;
105
+ }
106
+
107
+ module.exports = {
108
+ createInstanceNotImplemented,
109
+ createStaticNotImplemented,
110
+ setupNotImplemented,
111
+ };
@@ -7,6 +7,7 @@ const {
7
7
  isConstructor,
8
8
  isPromisified,
9
9
  getObjectKeys,
10
+ isExcludedPrototype,
10
11
  } = require("./util");
11
12
 
12
13
  const defaultSuffix = "Async";
@@ -22,12 +23,6 @@ const defaultPromisifier = (fn, _defaultPromisifier, options) => {
22
23
  });
23
24
  };
24
25
 
25
- const excludedPrototypes = [
26
- Object.getPrototypeOf(Array),
27
- Object.getPrototypeOf(Object),
28
- Object.getPrototypeOf(Function),
29
- ];
30
-
31
26
  const excludedClasses = [Array, Object, Function];
32
27
 
33
28
  // Helper function to determine if a class extends from any excluded class
@@ -54,17 +49,9 @@ function promisifyAll2(obj, options) {
54
49
  return;
55
50
  }
56
51
 
57
- const allKeys = getObjectKeys(obj, excludedPrototypes);
52
+ const allKeys = getObjectKeys(obj);
58
53
 
59
54
  for (const key of allKeys) {
60
- if (key.endsWith(options.suffix)) {
61
- throw new TypeError(
62
- "Cannot promisify an API that has normal methods with '%s'-suffix\u000a\u000a See http://goo.gl/MqrFmX\u000a".replace(
63
- "%s",
64
- options.suffix
65
- )
66
- );
67
- }
68
55
  const value = obj[key];
69
56
  const promisifiedKey = key + options.suffix;
70
57
  const passesDefaultFilter =
@@ -78,6 +65,16 @@ function promisifyAll2(obj, options) {
78
65
  ) {
79
66
  continue;
80
67
  }
68
+
69
+ if (key.endsWith(options.suffix)) {
70
+ throw new TypeError(
71
+ "Cannot promisify an API that has normal methods with '%s'-suffix\u000a\u000a See http://goo.gl/MqrFmX\u000a".replace(
72
+ "%s",
73
+ options.suffix
74
+ )
75
+ );
76
+ }
77
+
81
78
  obj[promisifiedKey] = options.promisifier(value, defaultPromisifier, {
82
79
  context: obj,
83
80
  copyProps: false,
@@ -110,7 +107,7 @@ function promisifyAll(target, _options) {
110
107
  );
111
108
  }
112
109
 
113
- const allKeys = getObjectKeys(target, excludedPrototypes);
110
+ const allKeys = getObjectKeys(target);
114
111
 
115
112
  for (const key of allKeys) {
116
113
  const value = target[key];
@@ -121,7 +118,7 @@ function promisifyAll(target, _options) {
121
118
  isClass(value)
122
119
  ) {
123
120
  const proto = Object.getPrototypeOf(value);
124
- if (!excludedPrototypes.includes(proto)) {
121
+ if (!isExcludedPrototype(proto)) {
125
122
  promisifyAll2(proto, options);
126
123
  }
127
124
 
package/lib/using.js CHANGED
@@ -3,6 +3,7 @@
3
3
  const { Disposer } = require("./disposer");
4
4
  const { isPromise } = require("./util");
5
5
 
6
+ const SYM_FN_DISPOSE = Symbol("fnDispose");
6
7
  /**
7
8
  * @description
8
9
  * The using function is a utility function that allows you to acquire resources,
@@ -37,10 +38,15 @@ function using(resources, handler, Promise, asArray) {
37
38
  });
38
39
 
39
40
  return Promise.map(promiseRes, async (resource) => {
40
- if (resource instanceof Disposer) {
41
+ if (
42
+ resource &&
43
+ (resource instanceof Disposer ||
44
+ (resource._promise && typeof resource._data === "function"))
45
+ ) {
41
46
  try {
42
47
  const res = await resource._promise;
43
48
  resource._result = res;
49
+ resource[SYM_FN_DISPOSE] = resource._data;
44
50
  } catch (error) {
45
51
  acquisitionErrors.push(error);
46
52
  resource._error = error;
@@ -68,18 +74,18 @@ function using(resources, handler, Promise, asArray) {
68
74
  const errors = [];
69
75
  return Promise.each(processedResources, async (resource) => {
70
76
  // dispose all resources that were acquired without errors
71
- if (!resource._error && resource.hasOwnProperty("_result")) {
72
- if (resource instanceof Disposer) {
73
- try {
74
- await resource._data(resource._result);
75
- } catch (error) {
76
- errors.push(error);
77
- }
77
+ if (resource && resource[SYM_FN_DISPOSE]) {
78
+ try {
79
+ await resource[SYM_FN_DISPOSE](resource._result);
80
+ } catch (error) {
81
+ errors.push(error);
78
82
  }
79
83
  }
80
84
  }).finally(() => {
81
85
  if (errors.length > 0) {
82
- Promise.___throwUncaughtError(new Error("cleanup resources failed"));
86
+ Promise.___throwUncaughtError(
87
+ new AggregateError(errors, "cleanup resources failed", errors)
88
+ );
83
89
  }
84
90
  });
85
91
  };
@@ -97,26 +103,25 @@ function using(resources, handler, Promise, asArray) {
97
103
  results.push(resource._result);
98
104
  }
99
105
 
100
- let handlerResult;
101
- let handlerSyncError;
106
+ let handlerPromise;
107
+
102
108
  try {
103
109
  // now call the handler with the results
104
- if (asArray) {
105
- handlerResult = handler(results);
106
- } else {
107
- handlerResult = handler(...results);
108
- }
110
+ handlerPromise = Promise.resolve(
111
+ asArray ? handler(results) : handler(...results)
112
+ );
109
113
  } catch (error) {
110
114
  // catch sync error from handler
111
- handlerSyncError = error;
115
+ handlerPromise = Promise.reject(error);
112
116
  }
113
117
 
114
- return disposeResources(processedResources).then(() => {
115
- if (handlerSyncError) {
116
- throw handlerSyncError;
117
- }
118
- return handlerResult;
119
- });
118
+ return handlerPromise
119
+ .tap(() => {
120
+ return disposeResources(processedResources);
121
+ })
122
+ .tapCatch(() => {
123
+ return disposeResources(processedResources);
124
+ });
120
125
  });
121
126
  }
122
127
 
package/lib/util.js CHANGED
@@ -9,43 +9,34 @@
9
9
  * @param {*} fn - The value to check
10
10
  * @returns {boolean} - True if the function is a class, false otherwise
11
11
  */
12
+ const thisAssignmentPattern = /this\s*\.\s*\S+\s*=/;
12
13
  function isClass(fn) {
13
- // Early return for non-functions or null/undefined
14
- if (!fn || typeof fn !== "function") return false;
15
-
16
- // Method 1: Check for ES6 class syntax
17
- // This detects class declarations and class expressions
18
14
  try {
19
- const fnStr = fn.toString();
20
- if (fnStr.startsWith("class ") || /^class\s+/.test(fnStr)) {
21
- return true;
15
+ if (typeof fn === "function") {
16
+ const keys = Object.getOwnPropertyNames(fn.prototype);
17
+
18
+ const fnStr = fn.toString();
19
+ const es6Class = fnStr.startsWith("class ") || /^class\s+/.test(fnStr);
20
+ const hasMethods = keys.length > 1;
21
+ const hasMethodsOtherThanConstructor =
22
+ keys.length > 0 && !(keys.length === 1 && keys[0] === "constructor");
23
+ const hasThisAssignmentAndStaticMethods =
24
+ thisAssignmentPattern.test(fnStr) &&
25
+ Object.getOwnPropertyNames(fn).length > 0;
26
+
27
+ if (
28
+ es6Class ||
29
+ hasMethods ||
30
+ hasMethodsOtherThanConstructor ||
31
+ hasThisAssignmentAndStaticMethods
32
+ ) {
33
+ return true;
34
+ }
22
35
  }
36
+ return false;
23
37
  } catch (e) {
24
- // Ignore errors that might occur when calling toString()
25
- // Some objects might have custom toString implementations that throw
26
38
  return false;
27
39
  }
28
-
29
- // Method 2: Check for constructor functions (ES5 class pattern)
30
- // A proper constructor has its .prototype.constructor pointing back to itself
31
- if (fn.prototype && fn.prototype.constructor === fn) {
32
- // Additional validation to filter out regular functions
33
- // that happen to have the correct prototype structure
34
-
35
- // Check if the prototype has any methods other than constructor
36
- // This is a strong indicator of a class-like structure
37
- const hasOwnMethods = Object.getOwnPropertyNames(fn.prototype).some(
38
- (name) =>
39
- name !== "constructor" && typeof fn.prototype[name] === "function"
40
- );
41
-
42
- // If it has prototype methods OR has static properties/methods
43
- // Either condition suggests it's being used as a class
44
- return hasOwnMethods || Object.getOwnPropertyNames(fn).length > 0;
45
- }
46
-
47
- // Not a class by any of our detection methods
48
- return false;
49
40
  }
50
41
 
51
42
  const rident = /^[a-z$_][a-z$_0-9]*$/i;
@@ -127,12 +118,25 @@ function isPromise(obj) {
127
118
  );
128
119
  }
129
120
 
121
+ // istanbul ignore next
122
+ const emptyFatArrow = () => {};
123
+ // istanbul ignore next
124
+ const emptyFunction = function () {};
125
+
130
126
  const defaultExcluded = [
131
127
  Object.getPrototypeOf(Array), // Array.prototype
132
128
  Object.getPrototypeOf(Object), // Object.prototype
133
129
  Object.getPrototypeOf(Function), // Function.prototype
130
+ Object.getPrototypeOf([]),
131
+ Object.getPrototypeOf({}),
132
+ Object.getPrototypeOf(emptyFatArrow),
133
+ Object.getPrototypeOf(emptyFunction),
134
134
  ];
135
135
 
136
+ function isExcludedPrototype(proto) {
137
+ return defaultExcluded.includes(proto);
138
+ }
139
+
136
140
  /**
137
141
  * Gets all property keys from an object and its prototype chain, excluding standard
138
142
  * prototypes like Object.prototype, Array.prototype, and Function.prototype
@@ -187,3 +191,4 @@ module.exports.isPromisified = isPromisified;
187
191
  module.exports.isPromise = isPromise;
188
192
  module.exports.triggerUncaughtException = triggerUncaughtException;
189
193
  module.exports.getObjectKeys = getObjectKeys;
194
+ module.exports.isExcludedPrototype = isExcludedPrototype;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aveazul",
3
- "version": "0.1.4",
4
- "description": "Bluebird-like APIs in extended native Promise",
3
+ "version": "1.0.0",
4
+ "description": "Bluebird drop-in replacement built on native Promise",
5
5
  "main": "lib/aveazul.js",
6
6
  "homepage": "https://github.com/jchip/aveazul",
7
7
  "license": "Apache-2.0",
@@ -11,9 +11,14 @@
11
11
  "test:coverage": "jest test --coverage",
12
12
  "test:bluebird": "USE_BLUEBIRD=true jest test",
13
13
  "test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand --no-coverage",
14
- "test:debug:bluebird": "USE_BLUEBIRD=true node --inspect-brk node_modules/.bin/jest --runInBand --no-coverage"
14
+ "test:debug:bluebird": "USE_BLUEBIRD=true node --inspect-brk node_modules/.bin/jest --runInBand --no-coverage",
15
+ "jest": "jest --no-coverage",
16
+ "jest:bluebird": "USE_BLUEBIRD=true jest --no-coverage"
15
17
  },
16
18
  "author": "Joel Chen",
19
+ "contributors": [
20
+ "Claude (AI assistant)"
21
+ ],
17
22
  "files": [
18
23
  "lib"
19
24
  ],
@@ -21,7 +26,34 @@
21
26
  "promise",
22
27
  "async",
23
28
  "bluebird",
24
- "nodejs"
29
+ "nodejs",
30
+ "node.js",
31
+ "promises",
32
+ "promise-library",
33
+ "native-promise",
34
+ "es6-promise",
35
+ "migration",
36
+ "promisify",
37
+ "utility",
38
+ "using",
39
+ "disposer",
40
+ "spread",
41
+ "promise-map",
42
+ "async-map",
43
+ "p-map",
44
+ "map-series",
45
+ "async-await",
46
+ "promise-chain",
47
+ "promise-utils",
48
+ "asynchronous",
49
+ "callback",
50
+ "promisifyall",
51
+ "timeout",
52
+ "delay",
53
+ "any",
54
+ "props",
55
+ "filter",
56
+ "reduce"
25
57
  ],
26
58
  "repository": {
27
59
  "type": "git",