aveazul 0.1.3 → 0.1.5
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 +52 -14
- package/lib/aveazul.js +221 -213
- package/lib/disposer.js +14 -0
- package/lib/not-implemented.js +97 -0
- package/lib/promisify-all.js +132 -0
- package/lib/promisify.js +70 -0
- package/lib/using.js +128 -0
- package/lib/util.js +194 -0
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
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)).
|
|
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.
|
|
6
|
+
|
|
7
|
+
Do you prefer Bluebird's API with native Promises? AveAzul gives you both - familiar Bluebird methods built on native Promise.
|
|
8
|
+
|
|
5
9
|
## Installation
|
|
6
10
|
|
|
7
11
|
```bash
|
|
@@ -11,29 +15,60 @@ npm install aveazul
|
|
|
11
15
|
## Usage
|
|
12
16
|
|
|
13
17
|
```javascript
|
|
14
|
-
const AveAzul = require(
|
|
18
|
+
const AveAzul = require("aveazul");
|
|
15
19
|
|
|
16
20
|
// Basic Promise usage
|
|
17
21
|
const promise = new AveAzul((resolve) => resolve(42));
|
|
18
|
-
promise.then(value => console.log(value)); // 42
|
|
22
|
+
promise.then((value) => console.log(value)); // 42
|
|
19
23
|
|
|
20
24
|
// Utility methods
|
|
21
25
|
AveAzul.resolve([1, 2, 3])
|
|
22
|
-
.map(x => x * 2)
|
|
23
|
-
.filter(x => x > 2)
|
|
24
|
-
.then(result => console.log(result)); // [4, 6]
|
|
26
|
+
.map((x) => x * 2)
|
|
27
|
+
.filter((x) => x > 2)
|
|
28
|
+
.then((result) => console.log(result)); // [4, 6]
|
|
25
29
|
|
|
26
30
|
// Promisify callback-style functions
|
|
27
|
-
const fs = require(
|
|
31
|
+
const fs = require("fs");
|
|
28
32
|
const readFile = AveAzul.promisify(fs.readFile);
|
|
29
|
-
readFile(
|
|
33
|
+
readFile("file.txt").then((content) => console.log(content));
|
|
34
|
+
// Properties from the original function are preserved
|
|
35
|
+
console.log(readFile.length); // Original function's length property
|
|
30
36
|
|
|
31
37
|
// Promisify all methods of an object
|
|
32
38
|
const obj = {
|
|
33
|
-
method(cb) {
|
|
39
|
+
method(cb) {
|
|
40
|
+
cb(null, "result");
|
|
41
|
+
},
|
|
34
42
|
};
|
|
35
43
|
AveAzul.promisifyAll(obj);
|
|
36
|
-
obj.methodAsync().then(result => console.log(result)); // 'result'
|
|
44
|
+
obj.methodAsync().then((result) => console.log(result)); // 'result'
|
|
45
|
+
|
|
46
|
+
// Resource management with disposer and using
|
|
47
|
+
const getResource = () => {
|
|
48
|
+
return AveAzul.resolve({
|
|
49
|
+
data: "important data",
|
|
50
|
+
close: () => console.log("Resource closed!"),
|
|
51
|
+
}).disposer((resource) => resource.close());
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
AveAzul.using(getResource(), (resource) => {
|
|
55
|
+
console.log(resource.data); // "important data"
|
|
56
|
+
return AveAzul.resolve("operation completed");
|
|
57
|
+
}).then((result) => {
|
|
58
|
+
console.log(result); // "operation completed"
|
|
59
|
+
// Resource is automatically closed here, even if an error occurred
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Using spread to apply array results as arguments
|
|
63
|
+
AveAzul.all([getUser(1), getPosts(1), getComments(1)]).spread(
|
|
64
|
+
(user, posts, comments) => {
|
|
65
|
+
// Instead of using .then(([user, posts, comments]) => {...})
|
|
66
|
+
console.log(
|
|
67
|
+
`User ${user.name} has ${posts.length} posts and ${comments.length} comments`
|
|
68
|
+
);
|
|
69
|
+
return { user, activity: { posts, comments } };
|
|
70
|
+
}
|
|
71
|
+
);
|
|
37
72
|
```
|
|
38
73
|
|
|
39
74
|
## API
|
|
@@ -47,15 +82,15 @@ obj.methodAsync().then(result => console.log(result)); // 'result'
|
|
|
47
82
|
- `each(fn)` - Iterate over array elements
|
|
48
83
|
- `delay(ms)` - Delay resolution
|
|
49
84
|
- `timeout(ms, message?)` - Reject after specified time
|
|
50
|
-
- `try(fn)` - Wrap sync/async functions
|
|
51
85
|
- `props(obj)` - Resolve object properties
|
|
52
|
-
- `
|
|
86
|
+
- `spread(fn)` - Apply array values as arguments to function
|
|
53
87
|
- `tapCatch(fn)` - Execute side effects on rejection
|
|
54
88
|
- `reduce(fn, initialValue?)` - Reduce array elements
|
|
55
89
|
- `throw(reason)` - Return rejected promise
|
|
56
90
|
- `catchThrow(reason)` - Catch and throw new error
|
|
57
91
|
- `catchReturn(value)` - Catch and return value
|
|
58
92
|
- `get(propertyPath)` - Retrieve property value
|
|
93
|
+
- `disposer(fn)` - Create a disposer for use with AveAzul.using() for resource cleanup
|
|
59
94
|
|
|
60
95
|
### Static Methods
|
|
61
96
|
|
|
@@ -64,11 +99,16 @@ obj.methodAsync().then(result => console.log(result)); // 'result'
|
|
|
64
99
|
- `try(fn)` - Wrap sync/async functions
|
|
65
100
|
- `props(obj)` - Resolve object properties
|
|
66
101
|
- `defer()` - Create a deferred promise
|
|
67
|
-
- `promisify(fn, options?)` - Convert callback-style functions to promises
|
|
102
|
+
- `promisify(fn, options?)` - Convert callback-style functions to promises (preserves original function properties)
|
|
103
|
+
- `fromNode(fn, options?)` - Convert Node-style callback functions to promise-returning functions
|
|
104
|
+
- `fromCallback(fn, options?)` - Alias for fromNode
|
|
68
105
|
- `each(items, fn)` - Iterate over array elements
|
|
69
106
|
- `reduce(array, fn, initialValue?)` - Reduce array elements
|
|
107
|
+
- `method(fn)` - Creates a method that returns a promise resolving to the value returned by the original function
|
|
70
108
|
- `throw(reason)` - Return rejected promise
|
|
71
109
|
- `promisifyAll(target, options?)` - Convert all methods of an object/class to promises
|
|
110
|
+
- `using(resources, fn)` - Manage resources with automatic cleanup
|
|
111
|
+
- `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
|
|
72
112
|
|
|
73
113
|
### PromisifyAll Options
|
|
74
114
|
|
|
@@ -76,8 +116,6 @@ obj.methodAsync().then(result => console.log(result)); // 'result'
|
|
|
76
116
|
- `filter` - Filter function to determine which methods to promisify
|
|
77
117
|
- `promisifier` - Custom function to handle promisification
|
|
78
118
|
- `multiArgs` (default: false) - Whether to support multiple callback arguments
|
|
79
|
-
- `excludeMain` (default: false) - Whether to exclude promisifying the main object/class
|
|
80
|
-
- `context` - The context (this) to use when calling methods
|
|
81
119
|
|
|
82
120
|
## Development
|
|
83
121
|
|
package/lib/aveazul.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const xaa = require("xaa");
|
|
4
|
-
const { promisify
|
|
4
|
+
const { promisify } = require("./promisify");
|
|
5
|
+
const { promisifyAll } = require("./promisify-all");
|
|
6
|
+
const { Disposer } = require("./disposer");
|
|
7
|
+
const { using } = require("./using");
|
|
8
|
+
const { isPromise, triggerUncaughtException } = require("./util");
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
11
|
* AveAzul ("Blue Bird" in Spanish) - Extended Promise class that provides Bluebird-like utility methods
|
|
@@ -28,8 +32,8 @@ class AveAzul extends Promise {
|
|
|
28
32
|
* @returns {Promise} Promise that resolves with the original value
|
|
29
33
|
*/
|
|
30
34
|
tap(fn) {
|
|
31
|
-
return this.then(value => {
|
|
32
|
-
fn(value);
|
|
35
|
+
return this.then(async (value) => {
|
|
36
|
+
await fn(value);
|
|
33
37
|
return value;
|
|
34
38
|
});
|
|
35
39
|
}
|
|
@@ -41,7 +45,7 @@ class AveAzul extends Promise {
|
|
|
41
45
|
* @returns {Promise} Promise that resolves with the filtered array
|
|
42
46
|
*/
|
|
43
47
|
filter(fn) {
|
|
44
|
-
return this.then(value => xaa.filter(value, fn));
|
|
48
|
+
return this.then((value) => xaa.filter(value, fn));
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
/**
|
|
@@ -51,7 +55,7 @@ class AveAzul extends Promise {
|
|
|
51
55
|
* @returns {Promise} Promise that resolves with the mapped array
|
|
52
56
|
*/
|
|
53
57
|
map(fn) {
|
|
54
|
-
return this.then(value => xaa.map(value, fn));
|
|
58
|
+
return this.then((value) => xaa.map(value, fn));
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
/**
|
|
@@ -71,7 +75,18 @@ class AveAzul extends Promise {
|
|
|
71
75
|
* @returns {Promise} Promise that resolves when iteration is complete
|
|
72
76
|
*/
|
|
73
77
|
each(fn) {
|
|
74
|
-
return this.then(value =>
|
|
78
|
+
return this.then(async (value) => {
|
|
79
|
+
const result = [];
|
|
80
|
+
for (let i = 0; i < value.length; i++) {
|
|
81
|
+
let x = value[i];
|
|
82
|
+
if (isPromise(x)) {
|
|
83
|
+
x = await x;
|
|
84
|
+
}
|
|
85
|
+
await fn(x, i, value.length);
|
|
86
|
+
result.push(x);
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
});
|
|
75
90
|
}
|
|
76
91
|
|
|
77
92
|
/**
|
|
@@ -80,7 +95,7 @@ class AveAzul extends Promise {
|
|
|
80
95
|
* @returns {Promise} Promise that resolves after the delay
|
|
81
96
|
*/
|
|
82
97
|
delay(ms) {
|
|
83
|
-
return
|
|
98
|
+
return xaa.delay(ms);
|
|
84
99
|
}
|
|
85
100
|
|
|
86
101
|
/**
|
|
@@ -89,43 +104,8 @@ class AveAzul extends Promise {
|
|
|
89
104
|
* @param {string} [message] - Optional error message
|
|
90
105
|
* @returns {Promise} Promise that rejects if timeout occurs
|
|
91
106
|
*/
|
|
92
|
-
timeout(ms, message = "
|
|
93
|
-
return
|
|
94
|
-
const timer = setTimeout(() => {
|
|
95
|
-
reject(new Error(message));
|
|
96
|
-
}, ms);
|
|
97
|
-
|
|
98
|
-
this.then(
|
|
99
|
-
value => {
|
|
100
|
-
clearTimeout(timer);
|
|
101
|
-
resolve(value);
|
|
102
|
-
},
|
|
103
|
-
err => {
|
|
104
|
-
clearTimeout(timer);
|
|
105
|
-
reject(err);
|
|
106
|
-
}
|
|
107
|
-
);
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Bluebird-style try() for wrapping sync/async functions
|
|
113
|
-
* @param {Function} fn - Function to execute
|
|
114
|
-
* @returns {Promise} Promise that resolves with the function's return value
|
|
115
|
-
*/
|
|
116
|
-
try(fn) {
|
|
117
|
-
return new AveAzul((resolve, reject) => {
|
|
118
|
-
try {
|
|
119
|
-
const result = fn();
|
|
120
|
-
if (result && typeof result.then === "function") {
|
|
121
|
-
result.then(resolve, reject);
|
|
122
|
-
} else {
|
|
123
|
-
resolve(result);
|
|
124
|
-
}
|
|
125
|
-
} catch (err) {
|
|
126
|
-
reject(err);
|
|
127
|
-
}
|
|
128
|
-
});
|
|
107
|
+
timeout(ms, message = "operation timed out") {
|
|
108
|
+
return AveAzul.resolve(xaa.timeout(ms, message).run(this));
|
|
129
109
|
}
|
|
130
110
|
|
|
131
111
|
/**
|
|
@@ -133,34 +113,18 @@ class AveAzul extends Promise {
|
|
|
133
113
|
* @param {Object} obj - Object with promise values
|
|
134
114
|
* @returns {Promise} Promise that resolves with an object of resolved values
|
|
135
115
|
*/
|
|
136
|
-
props(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
116
|
+
props() {
|
|
117
|
+
return this.then((value) => {
|
|
118
|
+
const keys = Object.keys(value);
|
|
119
|
+
const values = keys.map((k) => value[k]);
|
|
120
|
+
|
|
121
|
+
return AveAzul.all(values).then((results) => {
|
|
122
|
+
const resolved = {};
|
|
123
|
+
keys.forEach((k, i) => {
|
|
124
|
+
resolved[k] = results[i];
|
|
125
|
+
});
|
|
126
|
+
return resolved;
|
|
144
127
|
});
|
|
145
|
-
return resolved;
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Bluebird-style catchIf() with predicate matching
|
|
151
|
-
* @param {Function|Error} predicate - Error class or predicate function
|
|
152
|
-
* @param {Function} fn - Handler function
|
|
153
|
-
* @returns {Promise} Promise with conditional catch handler
|
|
154
|
-
*/
|
|
155
|
-
catchIf(predicate, fn) {
|
|
156
|
-
return this.catch(err => {
|
|
157
|
-
if (
|
|
158
|
-
(typeof predicate === "function" && !predicate.prototype && predicate(err)) ||
|
|
159
|
-
(typeof predicate === "function" && predicate.prototype && err instanceof predicate)
|
|
160
|
-
) {
|
|
161
|
-
return fn(err);
|
|
162
|
-
}
|
|
163
|
-
throw err;
|
|
164
128
|
});
|
|
165
129
|
}
|
|
166
130
|
|
|
@@ -170,7 +134,7 @@ class AveAzul extends Promise {
|
|
|
170
134
|
* @returns {Promise} Promise that maintains the rejection
|
|
171
135
|
*/
|
|
172
136
|
tapCatch(fn) {
|
|
173
|
-
return this.catch(err => {
|
|
137
|
+
return this.catch((err) => {
|
|
174
138
|
fn(err);
|
|
175
139
|
throw err;
|
|
176
140
|
});
|
|
@@ -184,7 +148,32 @@ class AveAzul extends Promise {
|
|
|
184
148
|
* @returns {Promise} Promise that resolves with the final reduced value
|
|
185
149
|
*/
|
|
186
150
|
reduce(fn, initialValue) {
|
|
187
|
-
|
|
151
|
+
const hasInitial = arguments.length > 1;
|
|
152
|
+
|
|
153
|
+
return this.then(async (array) => {
|
|
154
|
+
const len = array.length;
|
|
155
|
+
let value;
|
|
156
|
+
let idx;
|
|
157
|
+
if (hasInitial) {
|
|
158
|
+
idx = 0;
|
|
159
|
+
value = initialValue;
|
|
160
|
+
} else {
|
|
161
|
+
idx = 1;
|
|
162
|
+
value = array[0];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
value = isPromise(value) ? await value : value;
|
|
166
|
+
|
|
167
|
+
for (; idx < len; idx++) {
|
|
168
|
+
let x = array[idx];
|
|
169
|
+
if (isPromise(x)) {
|
|
170
|
+
x = await x;
|
|
171
|
+
}
|
|
172
|
+
value = await fn(value, x, idx, len);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return value;
|
|
176
|
+
});
|
|
188
177
|
}
|
|
189
178
|
|
|
190
179
|
/**
|
|
@@ -202,7 +191,9 @@ class AveAzul extends Promise {
|
|
|
202
191
|
* @returns {Promise} Promise that rejects with the new reason
|
|
203
192
|
*/
|
|
204
193
|
catchThrow(reason) {
|
|
205
|
-
return this.catch(() =>
|
|
194
|
+
return this.catch(() => {
|
|
195
|
+
throw reason;
|
|
196
|
+
});
|
|
206
197
|
}
|
|
207
198
|
|
|
208
199
|
/**
|
|
@@ -216,26 +207,50 @@ class AveAzul extends Promise {
|
|
|
216
207
|
|
|
217
208
|
/**
|
|
218
209
|
* Bluebird-style get() for retrieving a property value
|
|
219
|
-
* @param {string|number}
|
|
210
|
+
* @param {string|number} key - Key to retrieve
|
|
220
211
|
* @returns {Promise} Promise that resolves with the property value
|
|
221
212
|
*/
|
|
222
|
-
get(
|
|
223
|
-
return this.then(value =>
|
|
224
|
-
|
|
225
|
-
throw new TypeError("Cannot read property '" + propertyPath + "' of " + value);
|
|
226
|
-
}
|
|
213
|
+
get(key) {
|
|
214
|
+
return this.then((value) => value[key]);
|
|
215
|
+
}
|
|
227
216
|
|
|
228
|
-
|
|
229
|
-
|
|
217
|
+
/**
|
|
218
|
+
* Bluebird-style disposer() for resource cleanup
|
|
219
|
+
* @param {Function} fn - Cleanup function
|
|
220
|
+
* @returns {Disposer} Disposer object
|
|
221
|
+
*/
|
|
222
|
+
disposer(fn) {
|
|
223
|
+
if (typeof fn !== "function") {
|
|
224
|
+
throw new TypeError("Expected a function");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return new Disposer(fn, this);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Bluebird-style spread() method for handling array arguments
|
|
232
|
+
* Similar to Bluebird's Promise.prototype.spread()
|
|
233
|
+
* @param {Function} fn - Function to apply to the array arguments
|
|
234
|
+
* @returns {Promise} Promise that resolves with the function's return value
|
|
235
|
+
*/
|
|
236
|
+
spread(fn) {
|
|
237
|
+
if (typeof fn !== "function") {
|
|
238
|
+
return AveAzul.reject(
|
|
239
|
+
new TypeError("expecting a function but got " + fn)
|
|
240
|
+
);
|
|
241
|
+
}
|
|
230
242
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
243
|
+
return this.then(async (args) => {
|
|
244
|
+
if (Array.isArray(args)) {
|
|
245
|
+
for (let i = 0; i < args.length; i++) {
|
|
246
|
+
if (isPromise(args[i])) {
|
|
247
|
+
args[i] = await args[i];
|
|
248
|
+
}
|
|
234
249
|
}
|
|
235
|
-
|
|
250
|
+
return fn(...args);
|
|
251
|
+
} else {
|
|
252
|
+
return fn(args);
|
|
236
253
|
}
|
|
237
|
-
|
|
238
|
-
return result;
|
|
239
254
|
});
|
|
240
255
|
}
|
|
241
256
|
}
|
|
@@ -270,18 +285,18 @@ AveAzul.map = (value, fn) => AveAzul.resolve(xaa.map(value, fn));
|
|
|
270
285
|
* @param {Function} fn - Function to execute
|
|
271
286
|
* @returns {Promise} Promise that resolves with the function's return value
|
|
272
287
|
*/
|
|
273
|
-
AveAzul.try = fn => AveAzul.resolve(xaa.wrap(fn));
|
|
288
|
+
AveAzul.try = (fn) => AveAzul.resolve(xaa.wrap(fn));
|
|
274
289
|
|
|
275
290
|
/**
|
|
276
291
|
* Bluebird-style props() for object properties
|
|
277
292
|
* @param {Object} obj - Object with promise values
|
|
278
293
|
* @returns {Promise} Promise that resolves with an object of resolved values
|
|
279
294
|
*/
|
|
280
|
-
AveAzul.props = obj => {
|
|
295
|
+
AveAzul.props = (obj) => {
|
|
281
296
|
const keys = Object.keys(obj);
|
|
282
|
-
const values = keys.map(k => obj[k]);
|
|
297
|
+
const values = keys.map((k) => obj[k]);
|
|
283
298
|
|
|
284
|
-
return AveAzul.all(values).then(results => {
|
|
299
|
+
return AveAzul.all(values).then((results) => {
|
|
285
300
|
const resolved = {};
|
|
286
301
|
keys.forEach((k, i) => {
|
|
287
302
|
resolved[k] = results[i];
|
|
@@ -295,24 +310,7 @@ AveAzul.props = obj => {
|
|
|
295
310
|
* @returns {Object} Deferred object with promise, resolve, and reject methods
|
|
296
311
|
*/
|
|
297
312
|
AveAzul.defer = () => {
|
|
298
|
-
|
|
299
|
-
deferred.promise = AveAzul.resolve(deferred.promise);
|
|
300
|
-
return deferred;
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Bluebird-style promisify() for converting callback-style functions to promises
|
|
305
|
-
* @param {Function} fn - Function to promisify
|
|
306
|
-
* @param {Object} [options] - Options object
|
|
307
|
-
* @param {Object} [options.context] - `this` context to bind the function to
|
|
308
|
-
* @returns {Function} Promisified function that returns an AveAzul promise
|
|
309
|
-
*/
|
|
310
|
-
AveAzul.promisify = (fn, options = {}) => {
|
|
311
|
-
const promisified = nodePromisify(fn);
|
|
312
|
-
if (options.context) {
|
|
313
|
-
return (...args) => AveAzul.resolve(promisified.apply(options.context, args));
|
|
314
|
-
}
|
|
315
|
-
return (...args) => AveAzul.resolve(promisified(...args));
|
|
313
|
+
return xaa.makeDefer(AveAzul);
|
|
316
314
|
};
|
|
317
315
|
|
|
318
316
|
/**
|
|
@@ -321,7 +319,9 @@ AveAzul.promisify = (fn, options = {}) => {
|
|
|
321
319
|
* @param {Function} fn - Iterator function to call for each item
|
|
322
320
|
* @returns {Promise} Promise that resolves when iteration is complete
|
|
323
321
|
*/
|
|
324
|
-
AveAzul.each = (items, fn)
|
|
322
|
+
AveAzul.each = function (items, fn) {
|
|
323
|
+
return AveAzul.resolve(items).each(fn);
|
|
324
|
+
};
|
|
325
325
|
|
|
326
326
|
/**
|
|
327
327
|
* Bluebird-style reduce() for array reduction
|
|
@@ -330,124 +330,132 @@ AveAzul.each = (items, fn) => AveAzul.resolve(xaa.each(items, fn));
|
|
|
330
330
|
* @param {*} [initialValue] - Optional initial value
|
|
331
331
|
* @returns {Promise} Promise that resolves with the final reduced value
|
|
332
332
|
*/
|
|
333
|
-
AveAzul.reduce = (array,
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
return AveAzul.resolve().then(async () => {
|
|
338
|
-
let value = hasInitial ? initialValue : array[0];
|
|
339
|
-
const start = hasInitial ? 0 : 1;
|
|
340
|
-
|
|
341
|
-
for (let i = start; i < len; i++) {
|
|
342
|
-
value = await fn(value, array[i], i, len);
|
|
343
|
-
}
|
|
333
|
+
AveAzul.reduce = function (array, ...args) {
|
|
334
|
+
return AveAzul.resolve(array).reduce(...args);
|
|
335
|
+
};
|
|
344
336
|
|
|
345
|
-
|
|
337
|
+
/**
|
|
338
|
+
* Bluebird-style promisify() for converting callback-based functions to promises
|
|
339
|
+
* @param {Function} fn - Function to promisify
|
|
340
|
+
* @param {Object} [options] - Options object
|
|
341
|
+
* @returns {Function} Promisified function
|
|
342
|
+
*/
|
|
343
|
+
AveAzul.promisify = (fn, options) => {
|
|
344
|
+
return promisify(fn, {
|
|
345
|
+
...options,
|
|
346
|
+
Promise: AveAzul,
|
|
346
347
|
});
|
|
347
348
|
};
|
|
348
349
|
|
|
349
350
|
/**
|
|
350
|
-
* Bluebird-style
|
|
351
|
-
* @param {
|
|
352
|
-
* @
|
|
351
|
+
* Bluebird-style promisifyAll() for converting callback-based functions to promises
|
|
352
|
+
* @param {Object} target - Object to promisify
|
|
353
|
+
* @param {Object} [options] - Options object
|
|
354
|
+
* @returns {Object} Object with promisified methods
|
|
353
355
|
*/
|
|
354
|
-
AveAzul.
|
|
356
|
+
AveAzul.promisifyAll = (target, options) => {
|
|
357
|
+
return promisifyAll(target, { ...options, Promise: AveAzul });
|
|
358
|
+
};
|
|
355
359
|
|
|
356
360
|
/**
|
|
357
|
-
* Bluebird-style
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
* @param {Object|Function} target - The object or class to promisify
|
|
361
|
-
* @param {Object} [options] - Configuration options
|
|
362
|
-
* @param {string} [options.suffix='Async'] - Suffix to append to promisified method names
|
|
363
|
-
* @param {Function} [options.filter] - Filter function to determine which methods to promisify
|
|
364
|
-
* @param {Function} [options.promisifier] - Custom function to handle promisification
|
|
365
|
-
* @param {boolean} [options.multiArgs=false] - Whether to support multiple callback arguments
|
|
366
|
-
* @param {boolean} [options.excludeMain=false] - Whether to exclude promisifying the main object/class
|
|
367
|
-
* @param {Object} [options.context] - The context (this) to use when calling methods
|
|
368
|
-
* @returns {Object|Function} The promisified object or class
|
|
369
|
-
* @throws {TypeError} If target is null, undefined, or not an object/function
|
|
370
|
-
*
|
|
371
|
-
* @example
|
|
372
|
-
* // Promisify an object
|
|
373
|
-
* const obj = {
|
|
374
|
-
* method(cb) { cb(null, 'result'); }
|
|
375
|
-
* };
|
|
376
|
-
* AveAzul.promisifyAll(obj);
|
|
377
|
-
* const result = await obj.methodAsync();
|
|
378
|
-
*
|
|
379
|
-
* @example
|
|
380
|
-
* // Promisify a class
|
|
381
|
-
* class MyClass {
|
|
382
|
-
* method(cb) { cb(null, 'result'); }
|
|
383
|
-
* }
|
|
384
|
-
* AveAzul.promisifyAll(MyClass);
|
|
385
|
-
* const instance = new MyClass();
|
|
386
|
-
* const result = await instance.methodAsync();
|
|
387
|
-
*
|
|
388
|
-
* @example
|
|
389
|
-
* // With custom options
|
|
390
|
-
* const obj = {
|
|
391
|
-
* method(cb) { cb(null, 'result1', 'result2'); }
|
|
392
|
-
* };
|
|
393
|
-
* AveAzul.promisifyAll(obj, {
|
|
394
|
-
* suffix: 'Promise',
|
|
395
|
-
* multiArgs: true,
|
|
396
|
-
* filter: (name) => name === 'method'
|
|
397
|
-
* });
|
|
398
|
-
* const [result1, result2] = await obj.methodPromise();
|
|
361
|
+
* Bluebird-style method() for creating a method that returns a promise
|
|
362
|
+
* @param {Function} fn - Function to create a method for
|
|
363
|
+
* @returns {Function} Method function that returns a promise
|
|
399
364
|
*/
|
|
400
|
-
AveAzul.
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
!func.name.startsWith('promisifyAll')
|
|
409
|
-
);
|
|
410
|
-
},
|
|
411
|
-
promisifier = (fn, context, multiArgs) => {
|
|
412
|
-
if (multiArgs) {
|
|
413
|
-
return (...args) => {
|
|
414
|
-
return new AveAzul((resolve, reject) => {
|
|
415
|
-
args.push((err, ...results) => {
|
|
416
|
-
if (err) reject(err);
|
|
417
|
-
else resolve(results);
|
|
418
|
-
});
|
|
419
|
-
fn.apply(context, args);
|
|
420
|
-
});
|
|
421
|
-
};
|
|
365
|
+
AveAzul.method = (fn) => {
|
|
366
|
+
return function (...args) {
|
|
367
|
+
return new AveAzul((resolve, reject) => {
|
|
368
|
+
try {
|
|
369
|
+
const result = fn.call(this, ...args);
|
|
370
|
+
resolve(result);
|
|
371
|
+
} catch (error) {
|
|
372
|
+
reject(error);
|
|
422
373
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
excludeMain = false,
|
|
427
|
-
context = target
|
|
428
|
-
} = options;
|
|
429
|
-
|
|
430
|
-
if (target == null || (typeof target !== 'object' && typeof target !== 'function')) {
|
|
431
|
-
throw new TypeError('target must be an object');
|
|
432
|
-
}
|
|
374
|
+
});
|
|
375
|
+
};
|
|
376
|
+
};
|
|
433
377
|
|
|
434
|
-
|
|
435
|
-
|
|
378
|
+
/**
|
|
379
|
+
* Bluebird-style using() for resource management. There is only a static version of this method.
|
|
380
|
+
* After the handler finish and returns, regardless of whether it resolves or rejects, the resources will be disposed.
|
|
381
|
+
*
|
|
382
|
+
* @param {Disposer|Array<Disposer>} resources - Resource disposers, either an array of disposers or a variadic argument list
|
|
383
|
+
* @param {Function} handler - Handler function that will receive the resources as arguments
|
|
384
|
+
* @returns {Promise} Promise that resolves with handler result
|
|
385
|
+
*/
|
|
386
|
+
AveAzul.using = (resources, ...args) => {
|
|
387
|
+
if (args.length === 0) {
|
|
388
|
+
throw new TypeError("resrouces and handler function required");
|
|
389
|
+
}
|
|
436
390
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
391
|
+
if (Array.isArray(resources)) {
|
|
392
|
+
if (args.length > 1) {
|
|
393
|
+
throw new TypeError(
|
|
394
|
+
"only two arguments are allowed when passing an array of resources"
|
|
395
|
+
);
|
|
442
396
|
}
|
|
397
|
+
return using(resources, args[0], AveAzul, true);
|
|
443
398
|
}
|
|
399
|
+
const handler = args.pop();
|
|
400
|
+
return using([resources, ...args], handler, AveAzul, false);
|
|
401
|
+
};
|
|
444
402
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
403
|
+
/**
|
|
404
|
+
* Bluebird-style join() for joining promises
|
|
405
|
+
*
|
|
406
|
+
* @param {...Promise} args - Promises to join
|
|
407
|
+
* @param {Function} handler - Handler function to apply to the joined results
|
|
408
|
+
* @returns {Promise} Promise that resolves with the handler's return value
|
|
409
|
+
*/
|
|
410
|
+
AveAzul.join = function (...args) {
|
|
411
|
+
if (args.length > 1 && typeof args.at(-1) === "function") {
|
|
412
|
+
const handler = args.pop();
|
|
413
|
+
return AveAzul.all(args).then((results) => handler(...results));
|
|
414
|
+
} else {
|
|
415
|
+
return AveAzul.all(args);
|
|
448
416
|
}
|
|
449
|
-
|
|
450
|
-
return target;
|
|
451
417
|
};
|
|
452
418
|
|
|
419
|
+
function fromCallback(fn, options) {
|
|
420
|
+
return new AveAzul((resolve, reject) => {
|
|
421
|
+
try {
|
|
422
|
+
fn((err, ...args) => {
|
|
423
|
+
if (err) {
|
|
424
|
+
reject(err);
|
|
425
|
+
} else {
|
|
426
|
+
if (options && options.multiArgs) {
|
|
427
|
+
resolve(args);
|
|
428
|
+
} else {
|
|
429
|
+
resolve(args[0]);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
} catch (err) {
|
|
434
|
+
reject(err);
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
AveAzul.fromNode = fromCallback;
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Bluebird-style fromCallback() for converting callback-based functions to promises
|
|
443
|
+
* @param {Function} fn - Function to convert
|
|
444
|
+
* @param {Object} [options] - Options object
|
|
445
|
+
* @returns {Promise} Promise that resolves with the function's return value
|
|
446
|
+
*/
|
|
447
|
+
AveAzul.fromCallback = fromCallback;
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* @description
|
|
451
|
+
* When fatal error and AveAzul needs to crash the process,
|
|
452
|
+
* this method is used to throw the error.
|
|
453
|
+
*
|
|
454
|
+
* @param {Error} error - The error to throw.
|
|
455
|
+
*/
|
|
456
|
+
AveAzul.___throwUncaughtError = triggerUncaughtException;
|
|
457
|
+
|
|
453
458
|
module.exports = AveAzul;
|
|
459
|
+
|
|
460
|
+
const { setupNotImplemented } = require("./not-implemented");
|
|
461
|
+
setupNotImplemented(AveAzul);
|
package/lib/disposer.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Disposer class for resource cleanup
|
|
5
|
+
* @private
|
|
6
|
+
*/
|
|
7
|
+
class Disposer {
|
|
8
|
+
constructor(fn, promise) {
|
|
9
|
+
this._data = fn; // The cleanup function
|
|
10
|
+
this._promise = promise; // The promise that resolves to the resource
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports.Disposer = Disposer;
|
|
@@ -0,0 +1,97 @@
|
|
|
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
|
+
"spread",
|
|
14
|
+
"error",
|
|
15
|
+
"bind",
|
|
16
|
+
"join",
|
|
17
|
+
"try",
|
|
18
|
+
"method",
|
|
19
|
+
"isFulfilled",
|
|
20
|
+
"isRejected",
|
|
21
|
+
"isPending",
|
|
22
|
+
"value",
|
|
23
|
+
"reason",
|
|
24
|
+
"props",
|
|
25
|
+
"any",
|
|
26
|
+
"some",
|
|
27
|
+
"map",
|
|
28
|
+
"reduce",
|
|
29
|
+
"filter",
|
|
30
|
+
"each",
|
|
31
|
+
"mapSeries",
|
|
32
|
+
"tap",
|
|
33
|
+
"tapCatch",
|
|
34
|
+
"catchThrow",
|
|
35
|
+
"catchReturn",
|
|
36
|
+
"get",
|
|
37
|
+
"throw",
|
|
38
|
+
"call",
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const proto = AveAzul.prototype;
|
|
42
|
+
const ret = [];
|
|
43
|
+
for (const method of methods) {
|
|
44
|
+
if (!proto[method]) {
|
|
45
|
+
ret.push(method);
|
|
46
|
+
proto[method] = createNotImplemented("instance " + method);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return ret;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function createStaticNotImplemented(AveAzul) {
|
|
53
|
+
const methods = [
|
|
54
|
+
"join",
|
|
55
|
+
"try",
|
|
56
|
+
"method",
|
|
57
|
+
"resolve",
|
|
58
|
+
"reject",
|
|
59
|
+
"all",
|
|
60
|
+
"props",
|
|
61
|
+
"any",
|
|
62
|
+
"some",
|
|
63
|
+
"map",
|
|
64
|
+
"filter",
|
|
65
|
+
"each",
|
|
66
|
+
"mapSeries",
|
|
67
|
+
"race",
|
|
68
|
+
"promisify",
|
|
69
|
+
"promisifyAll",
|
|
70
|
+
"fromNode",
|
|
71
|
+
"fromCallback",
|
|
72
|
+
"delay",
|
|
73
|
+
"coroutine",
|
|
74
|
+
"config",
|
|
75
|
+
];
|
|
76
|
+
const ret = [];
|
|
77
|
+
for (const method of methods) {
|
|
78
|
+
if (!AveAzul[method]) {
|
|
79
|
+
ret.push(method);
|
|
80
|
+
AveAzul[method] = createNotImplemented("static " + method);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return ret;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function setupNotImplemented(AveAzul) {
|
|
87
|
+
const instance = createInstanceNotImplemented(AveAzul);
|
|
88
|
+
const staticMethods = createStaticNotImplemented(AveAzul);
|
|
89
|
+
AveAzul.__notImplementedInstance = instance;
|
|
90
|
+
AveAzul.__notImplementedStatic = staticMethods;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = {
|
|
94
|
+
createInstanceNotImplemented,
|
|
95
|
+
createStaticNotImplemented,
|
|
96
|
+
setupNotImplemented,
|
|
97
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { promisify } = require("./promisify");
|
|
4
|
+
const {
|
|
5
|
+
isIdentifier,
|
|
6
|
+
isClass,
|
|
7
|
+
isConstructor,
|
|
8
|
+
isPromisified,
|
|
9
|
+
getObjectKeys,
|
|
10
|
+
isExcludedPrototype,
|
|
11
|
+
} = require("./util");
|
|
12
|
+
|
|
13
|
+
const defaultSuffix = "Async";
|
|
14
|
+
|
|
15
|
+
const defaultFilter = function (name) {
|
|
16
|
+
return isIdentifier(name) && name.charAt(0) !== "_" && name !== "constructor";
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const defaultPromisifier = (fn, _defaultPromisifier, options) => {
|
|
20
|
+
return promisify(fn, {
|
|
21
|
+
...options,
|
|
22
|
+
copyProps: false,
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const excludedClasses = [Array, Object, Function];
|
|
27
|
+
|
|
28
|
+
// Helper function to determine if a class extends from any excluded class
|
|
29
|
+
function isExcludedClass(obj) {
|
|
30
|
+
if (excludedClasses.includes(obj)) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Check if obj extends from any excluded class using instanceof
|
|
35
|
+
if (typeof obj === "function" && obj.prototype) {
|
|
36
|
+
// Check if prototype is instance of any excluded class
|
|
37
|
+
for (const excludedClass of excludedClasses) {
|
|
38
|
+
if (obj.prototype instanceof excludedClass) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function promisifyAll2(obj, options) {
|
|
48
|
+
if (isExcludedClass(obj)) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const allKeys = getObjectKeys(obj);
|
|
53
|
+
|
|
54
|
+
for (const key of allKeys) {
|
|
55
|
+
const value = obj[key];
|
|
56
|
+
const promisifiedKey = key + options.suffix;
|
|
57
|
+
const passesDefaultFilter =
|
|
58
|
+
options.filter === defaultFilter ? true : defaultFilter(key, value, obj);
|
|
59
|
+
if (
|
|
60
|
+
isConstructor(value) ||
|
|
61
|
+
typeof value !== "function" ||
|
|
62
|
+
isPromisified(value) ||
|
|
63
|
+
obj[promisifiedKey] ||
|
|
64
|
+
!options.filter(key, value, obj, passesDefaultFilter)
|
|
65
|
+
) {
|
|
66
|
+
continue;
|
|
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
|
+
|
|
78
|
+
obj[promisifiedKey] = options.promisifier(value, defaultPromisifier, {
|
|
79
|
+
context: obj,
|
|
80
|
+
copyProps: false,
|
|
81
|
+
multiArgs: options.multiArgs,
|
|
82
|
+
Promise: options.Promise,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function promisifyAll(target, _options) {
|
|
88
|
+
if (typeof target !== "function" && typeof target !== "object") {
|
|
89
|
+
throw new TypeError(
|
|
90
|
+
"the target of promisifyAll must be an object or a function"
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const options = {
|
|
95
|
+
suffix: defaultSuffix,
|
|
96
|
+
filter: defaultFilter,
|
|
97
|
+
promisifier: defaultPromisifier,
|
|
98
|
+
Promise: global.Promise,
|
|
99
|
+
..._options,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const suffix = options.suffix;
|
|
103
|
+
|
|
104
|
+
if (!isIdentifier(suffix)) {
|
|
105
|
+
throw new RangeError(
|
|
106
|
+
"suffix must be a valid identifier\u000a\u000a See http://goo.gl/MqrFmX\u000a"
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const allKeys = getObjectKeys(target);
|
|
111
|
+
|
|
112
|
+
for (const key of allKeys) {
|
|
113
|
+
const value = target[key];
|
|
114
|
+
if (
|
|
115
|
+
value &&
|
|
116
|
+
key !== "constructor" &&
|
|
117
|
+
!key.startsWith("_") &&
|
|
118
|
+
isClass(value)
|
|
119
|
+
) {
|
|
120
|
+
const proto = Object.getPrototypeOf(value);
|
|
121
|
+
if (!isExcludedPrototype(proto)) {
|
|
122
|
+
promisifyAll2(proto, options);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
promisifyAll2(value, options);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
promisifyAll2(target, options);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports.promisifyAll = promisifyAll;
|
package/lib/promisify.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { copyOwnProperties, isPromisified } = require("./util");
|
|
4
|
+
|
|
5
|
+
module.exports.promisify = function promisify(fn, _options) {
|
|
6
|
+
if (typeof fn !== "function") {
|
|
7
|
+
throw new TypeError("expecting a function but got " + {}.toString.call(fn));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (isPromisified(fn)) {
|
|
11
|
+
return fn;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const options = {
|
|
15
|
+
Promise: global.Promise,
|
|
16
|
+
multiArgs: false,
|
|
17
|
+
copyProps: true,
|
|
18
|
+
suffix: "",
|
|
19
|
+
..._options,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const Promise = options.Promise;
|
|
23
|
+
const multiArgs = !!options.multiArgs;
|
|
24
|
+
|
|
25
|
+
const promisifiedFn = (...args) => {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
// add a callback to the end of the arguments to transfer the result to the promise
|
|
28
|
+
args.push((err, ...values) => {
|
|
29
|
+
if (err) {
|
|
30
|
+
return reject(err);
|
|
31
|
+
}
|
|
32
|
+
if (multiArgs) {
|
|
33
|
+
resolve(values);
|
|
34
|
+
} else {
|
|
35
|
+
resolve(values[0]);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// call the original function with the updated args
|
|
40
|
+
fn.call(options.context, ...args);
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
if (options.copyProps) {
|
|
45
|
+
copyOwnProperties(fn, promisifiedFn);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
Object.defineProperty(promisifiedFn, "__isPromisified__", {
|
|
49
|
+
value: true,
|
|
50
|
+
writable: false,
|
|
51
|
+
enumerable: false,
|
|
52
|
+
configurable: true,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
Object.defineProperty(promisifiedFn, "length", {
|
|
56
|
+
value: fn.length,
|
|
57
|
+
writable: false,
|
|
58
|
+
enumerable: false,
|
|
59
|
+
configurable: false,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
Object.defineProperty(promisifiedFn, "name", {
|
|
63
|
+
value: fn.name + options.suffix,
|
|
64
|
+
writable: false,
|
|
65
|
+
enumerable: false,
|
|
66
|
+
configurable: false,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return promisifiedFn;
|
|
70
|
+
};
|
package/lib/using.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { Disposer } = require("./disposer");
|
|
4
|
+
const { isPromise } = require("./util");
|
|
5
|
+
|
|
6
|
+
const SYM_FN_DISPOSE = Symbol("fnDispose");
|
|
7
|
+
/**
|
|
8
|
+
* @description
|
|
9
|
+
* The using function is a utility function that allows you to acquire resources,
|
|
10
|
+
* process them, and then dispose of them in an error-safe manner.
|
|
11
|
+
*
|
|
12
|
+
* @param {Array} resources - An array of resources to acquire.
|
|
13
|
+
* @param {Function} handler - A function that will be called with the acquired resources.
|
|
14
|
+
* @param {Promise} Promise - The Promise implementation to use. AveAzul or Bluebird.
|
|
15
|
+
* @param {boolean} asArray - Whether to return the result as an array.
|
|
16
|
+
* @returns {Promise} A promise that resolves to the result of the handler function.
|
|
17
|
+
*/
|
|
18
|
+
function using(resources, handler, Promise, asArray) {
|
|
19
|
+
if (typeof handler !== "function") {
|
|
20
|
+
throw new TypeError("handler must be a function");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// resources is guaranateed to be an array of disposer, promise like, or any value
|
|
24
|
+
// first process all resources by mapping the resources array:
|
|
25
|
+
// 1. if it's a disposer, get its promise and resolve its value
|
|
26
|
+
// 2. if it's a promise like, get its value
|
|
27
|
+
// 3. otherwise, return the value
|
|
28
|
+
// Expect Promise to be AveAzul or Bluebird that has map method
|
|
29
|
+
const acquisitionErrors = [];
|
|
30
|
+
|
|
31
|
+
const acquireResources = () => {
|
|
32
|
+
const promiseRes = resources.map((resource) => {
|
|
33
|
+
// if it's a promise-like, wait for its resolved value
|
|
34
|
+
if (isPromise(resource)) {
|
|
35
|
+
return { ___promise: resource };
|
|
36
|
+
}
|
|
37
|
+
return resource;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return Promise.map(promiseRes, async (resource) => {
|
|
41
|
+
if (
|
|
42
|
+
resource &&
|
|
43
|
+
(resource instanceof Disposer ||
|
|
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;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// if it's a promise like, wait for its resolved value
|
|
58
|
+
if (resource && resource.___promise) {
|
|
59
|
+
try {
|
|
60
|
+
const res = await resource.___promise;
|
|
61
|
+
resource._result = res;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
acquisitionErrors.push(error);
|
|
64
|
+
resource._error = error;
|
|
65
|
+
}
|
|
66
|
+
return resource;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { _result: resource };
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const disposeResources = (processedResources) => {
|
|
74
|
+
const errors = [];
|
|
75
|
+
return Promise.each(processedResources, async (resource) => {
|
|
76
|
+
// dispose all resources that were acquired without errors
|
|
77
|
+
if (resource && resource[SYM_FN_DISPOSE]) {
|
|
78
|
+
try {
|
|
79
|
+
await resource[SYM_FN_DISPOSE](resource._result);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
errors.push(error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}).finally(() => {
|
|
85
|
+
if (errors.length > 0) {
|
|
86
|
+
Promise.___throwUncaughtError(
|
|
87
|
+
new AggregateError(errors, "cleanup resources failed", errors)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return acquireResources().then((processedResources) => {
|
|
94
|
+
if (acquisitionErrors.length > 0) {
|
|
95
|
+
return disposeResources(processedResources).tap(() => {
|
|
96
|
+
throw acquisitionErrors[0];
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// now collect all the results into an array
|
|
101
|
+
const results = [];
|
|
102
|
+
for (const resource of processedResources) {
|
|
103
|
+
results.push(resource._result);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let handlerPromise;
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
// now call the handler with the results
|
|
110
|
+
handlerPromise = Promise.resolve(
|
|
111
|
+
asArray ? handler(results) : handler(...results)
|
|
112
|
+
);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
// catch sync error from handler
|
|
115
|
+
handlerPromise = Promise.reject(error);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return handlerPromise
|
|
119
|
+
.tap(() => {
|
|
120
|
+
return disposeResources(processedResources);
|
|
121
|
+
})
|
|
122
|
+
.tapCatch(() => {
|
|
123
|
+
return disposeResources(processedResources);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports.using = using;
|
package/lib/util.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Determines if a function is a class (either ES6 class or ES5 constructor function)
|
|
5
|
+
* This function performs several checks to identify different class patterns:
|
|
6
|
+
* 1. ES6 classes with the 'class' keyword
|
|
7
|
+
* 2. Constructor functions (ES5 classes) with prototype methods
|
|
8
|
+
*
|
|
9
|
+
* @param {*} fn - The value to check
|
|
10
|
+
* @returns {boolean} - True if the function is a class, false otherwise
|
|
11
|
+
*/
|
|
12
|
+
const thisAssignmentPattern = /this\s*\.\s*\S+\s*=/;
|
|
13
|
+
function isClass(fn) {
|
|
14
|
+
try {
|
|
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
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
} catch (e) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const rident = /^[a-z$_][a-z$_0-9]*$/i;
|
|
43
|
+
function isIdentifier(str) {
|
|
44
|
+
return rident.test(str);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isConstructor(func) {
|
|
48
|
+
if (!func) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
const proto = func.prototype;
|
|
52
|
+
return (
|
|
53
|
+
!!proto &&
|
|
54
|
+
!!proto.constructor &&
|
|
55
|
+
!!proto.constructor.name &&
|
|
56
|
+
proto.constructor.name === func.name
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Prop filtering code copied from bluebird/js/release
|
|
62
|
+
*/
|
|
63
|
+
const noCopyProps = [
|
|
64
|
+
"arity",
|
|
65
|
+
"length",
|
|
66
|
+
"name",
|
|
67
|
+
"arguments",
|
|
68
|
+
"caller",
|
|
69
|
+
"callee",
|
|
70
|
+
"prototype",
|
|
71
|
+
"__isPromisified__",
|
|
72
|
+
];
|
|
73
|
+
const noCopyPropsPattern = new RegExp("^(?:" + noCopyProps.join("|") + ")$");
|
|
74
|
+
|
|
75
|
+
function propsFilter(key) {
|
|
76
|
+
return !noCopyPropsPattern.test(key);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function copyOwnProperties(source, target, filter = propsFilter) {
|
|
80
|
+
const names = Object.getOwnPropertyNames(source);
|
|
81
|
+
|
|
82
|
+
for (const name of names) {
|
|
83
|
+
if (filter(name)) {
|
|
84
|
+
Object.defineProperty(
|
|
85
|
+
target,
|
|
86
|
+
name,
|
|
87
|
+
Object.getOwnPropertyDescriptor(source, name)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Copied from bluebird/js/release/util.js
|
|
95
|
+
* @param {*} fn
|
|
96
|
+
* @returns {boolean}
|
|
97
|
+
*/
|
|
98
|
+
function isPromisified(fn) {
|
|
99
|
+
try {
|
|
100
|
+
return fn.__isPromisified__ === true;
|
|
101
|
+
} catch (e) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Determines if an object is a Promise instance
|
|
108
|
+
* @param {*} obj - The object to check
|
|
109
|
+
* @returns {boolean} - True if the object is a Promise instance, false otherwise
|
|
110
|
+
*/
|
|
111
|
+
function isPromise(obj) {
|
|
112
|
+
return (
|
|
113
|
+
obj instanceof Promise ||
|
|
114
|
+
(obj != null &&
|
|
115
|
+
typeof obj === "object" &&
|
|
116
|
+
typeof obj.then === "function" &&
|
|
117
|
+
typeof obj.catch === "function")
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// istanbul ignore next
|
|
122
|
+
const emptyFatArrow = () => {};
|
|
123
|
+
// istanbul ignore next
|
|
124
|
+
const emptyFunction = function () {};
|
|
125
|
+
|
|
126
|
+
const defaultExcluded = [
|
|
127
|
+
Object.getPrototypeOf(Array), // Array.prototype
|
|
128
|
+
Object.getPrototypeOf(Object), // Object.prototype
|
|
129
|
+
Object.getPrototypeOf(Function), // Function.prototype
|
|
130
|
+
Object.getPrototypeOf([]),
|
|
131
|
+
Object.getPrototypeOf({}),
|
|
132
|
+
Object.getPrototypeOf(emptyFatArrow),
|
|
133
|
+
Object.getPrototypeOf(emptyFunction),
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
function isExcludedPrototype(proto) {
|
|
137
|
+
return defaultExcluded.includes(proto);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Gets all property keys from an object and its prototype chain, excluding standard
|
|
142
|
+
* prototypes like Object.prototype, Array.prototype, and Function.prototype
|
|
143
|
+
*
|
|
144
|
+
* @param {Object} target - The target object to get keys from
|
|
145
|
+
* @param {Array} [excludedPrototypes=[]] - An array of prototype objects to exclude keys from
|
|
146
|
+
* @returns {Array<string>} - Array of property keys
|
|
147
|
+
*/
|
|
148
|
+
function getObjectKeys(target, excludedPrototypes = []) {
|
|
149
|
+
const excluded =
|
|
150
|
+
excludedPrototypes.length > 0 ? excludedPrototypes : defaultExcluded;
|
|
151
|
+
|
|
152
|
+
// Get own properties
|
|
153
|
+
const ownKeys = Object.getOwnPropertyNames(target);
|
|
154
|
+
|
|
155
|
+
// Get prototype properties, excluding those from excluded prototypes
|
|
156
|
+
let protoKeys = [];
|
|
157
|
+
let currentProto = Object.getPrototypeOf(target);
|
|
158
|
+
|
|
159
|
+
// Walk up the prototype chain until we hit null or an excluded prototype
|
|
160
|
+
while (currentProto && !excluded.includes(currentProto)) {
|
|
161
|
+
protoKeys = [...protoKeys, ...Object.getOwnPropertyNames(currentProto)];
|
|
162
|
+
currentProto = Object.getPrototypeOf(currentProto);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Combine own properties and prototype properties
|
|
166
|
+
return [...protoKeys, ...ownKeys];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Triggers an uncaught exception in a safe way by scheduling it on the next event loop tick
|
|
171
|
+
* This is used for fatal errors that should crash the process
|
|
172
|
+
* @param {Error} error - The error to throw
|
|
173
|
+
*/
|
|
174
|
+
function triggerUncaughtException(error) {
|
|
175
|
+
if (!(error instanceof Error)) {
|
|
176
|
+
error = new Error(String(error));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Use setTimeout with 0ms delay to throw on the next event loop tick
|
|
180
|
+
// This ensures the current execution context completes first
|
|
181
|
+
setTimeout(() => {
|
|
182
|
+
throw error;
|
|
183
|
+
}, 0);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
module.exports.copyOwnProperties = copyOwnProperties;
|
|
187
|
+
module.exports.isClass = isClass;
|
|
188
|
+
module.exports.isIdentifier = isIdentifier;
|
|
189
|
+
module.exports.isConstructor = isConstructor;
|
|
190
|
+
module.exports.isPromisified = isPromisified;
|
|
191
|
+
module.exports.isPromise = isPromise;
|
|
192
|
+
module.exports.triggerUncaughtException = triggerUncaughtException;
|
|
193
|
+
module.exports.getObjectKeys = getObjectKeys;
|
|
194
|
+
module.exports.isExcludedPrototype = isExcludedPrototype;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aveazul",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Bluebird-like APIs in extended native Promise",
|
|
5
5
|
"main": "lib/aveazul.js",
|
|
6
6
|
"homepage": "https://github.com/jchip/aveazul",
|
|
@@ -8,7 +8,12 @@
|
|
|
8
8
|
"scripts": {
|
|
9
9
|
"test": "jest test",
|
|
10
10
|
"test:watch": "jest test --watch",
|
|
11
|
-
"test:coverage": "jest test --coverage"
|
|
11
|
+
"test:coverage": "jest test --coverage",
|
|
12
|
+
"test:bluebird": "USE_BLUEBIRD=true jest test",
|
|
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",
|
|
15
|
+
"jest": "jest --no-coverage",
|
|
16
|
+
"jest:bluebird": "USE_BLUEBIRD=true jest --no-coverage"
|
|
12
17
|
},
|
|
13
18
|
"author": "Joel Chen",
|
|
14
19
|
"files": [
|
|
@@ -28,6 +33,8 @@
|
|
|
28
33
|
"xaa": "^1.7.3"
|
|
29
34
|
},
|
|
30
35
|
"devDependencies": {
|
|
31
|
-
"
|
|
36
|
+
"bluebird": "^3.7.2",
|
|
37
|
+
"jest": "^29.7.0",
|
|
38
|
+
"rimraf": "^3.0.1"
|
|
32
39
|
}
|
|
33
40
|
}
|