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 +80 -4
- package/lib/aveazul.js +271 -6
- package/lib/disposer.js +2 -2
- package/lib/not-implemented.js +111 -0
- package/lib/promisify-all.js +14 -17
- package/lib/using.js +28 -23
- package/lib/util.js +36 -31
- package/package.json +36 -4
package/README.md
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
# AveAzul
|
|
2
2
|
|
|
3
|
-
AveAzul ("Blue Bird" in Spanish)
|
|
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
|
-
|
|
5
|
+
## Purpose
|
|
6
6
|
|
|
7
|
-
|
|
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
|
-
*
|
|
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
|
|
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(
|
|
9
|
-
this._data =
|
|
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
|
+
};
|
package/lib/promisify-all.js
CHANGED
|
@@ -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
|
|
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
|
|
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 (!
|
|
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 (
|
|
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 (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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(
|
|
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
|
|
101
|
-
|
|
106
|
+
let handlerPromise;
|
|
107
|
+
|
|
102
108
|
try {
|
|
103
109
|
// now call the handler with the results
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
115
|
+
handlerPromise = Promise.reject(error);
|
|
112
116
|
}
|
|
113
117
|
|
|
114
|
-
return
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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.
|
|
4
|
-
"description": "Bluebird-
|
|
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",
|