aveazul 0.1.2 → 0.1.4
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 +87 -51
- package/lib/aveazul.js +149 -219
- package/lib/disposer.js +14 -0
- package/lib/promisify-all.js +135 -0
- package/lib/promisify.js +70 -0
- package/lib/using.js +123 -0
- package/lib/util.js +189 -0
- package/package.json +9 -4
package/README.md
CHANGED
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
# AveAzul
|
|
2
2
|
|
|
3
|
-
AveAzul ("Blue Bird" in Spanish) is a Promise
|
|
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
|
-
|
|
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
6
|
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
## Features
|
|
10
|
-
|
|
11
|
-
- Extends native Promise with Bluebird-like utility methods
|
|
12
|
-
- Built on top of the efficient `xaa` library for async operations
|
|
13
|
-
- Zero external runtime dependencies (other than `xaa`)
|
|
14
|
-
- TypeScript-friendly
|
|
15
|
-
- Familiar Bluebird-style API
|
|
7
|
+
Do you prefer Bluebird's API with native Promises? AveAzul gives you both - familiar Bluebird methods built on native Promise.
|
|
16
8
|
|
|
17
9
|
## Installation
|
|
18
10
|
|
|
@@ -23,65 +15,109 @@ npm install aveazul
|
|
|
23
15
|
## Usage
|
|
24
16
|
|
|
25
17
|
```javascript
|
|
26
|
-
const AveAzul = require(
|
|
27
|
-
|
|
28
|
-
// Create a new promise
|
|
29
|
-
const promise = new AveAzul((resolve, reject) => {
|
|
30
|
-
setTimeout(() => resolve('result'), 1000);
|
|
31
|
-
});
|
|
18
|
+
const AveAzul = require("aveazul");
|
|
32
19
|
|
|
33
|
-
//
|
|
34
|
-
promise
|
|
35
|
-
|
|
36
|
-
.delay(500)
|
|
37
|
-
.then(value => console.log('After delay:', value));
|
|
20
|
+
// Basic Promise usage
|
|
21
|
+
const promise = new AveAzul((resolve) => resolve(42));
|
|
22
|
+
promise.then((value) => console.log(value)); // 42
|
|
38
23
|
|
|
39
|
-
//
|
|
40
|
-
AveAzul.delay(1000, 'hello')
|
|
41
|
-
.then(value => console.log(value));
|
|
42
|
-
|
|
43
|
-
// Array operations
|
|
24
|
+
// Utility methods
|
|
44
25
|
AveAzul.resolve([1, 2, 3])
|
|
45
|
-
.map(x => x * 2)
|
|
46
|
-
.filter(x => x >
|
|
47
|
-
.then(result => console.log(result)); // [6]
|
|
26
|
+
.map((x) => x * 2)
|
|
27
|
+
.filter((x) => x > 2)
|
|
28
|
+
.then((result) => console.log(result)); // [4, 6]
|
|
29
|
+
|
|
30
|
+
// Promisify callback-style functions
|
|
31
|
+
const fs = require("fs");
|
|
32
|
+
const readFile = AveAzul.promisify(fs.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
|
|
36
|
+
|
|
37
|
+
// Promisify all methods of an object
|
|
38
|
+
const obj = {
|
|
39
|
+
method(cb) {
|
|
40
|
+
cb(null, "result");
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
AveAzul.promisifyAll(obj);
|
|
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
|
+
});
|
|
48
61
|
```
|
|
49
62
|
|
|
50
63
|
## API
|
|
51
64
|
|
|
52
65
|
### Instance Methods
|
|
53
66
|
|
|
54
|
-
- `tap(fn)` - Execute side effects
|
|
67
|
+
- `tap(fn)` - Execute side effects and return original value
|
|
55
68
|
- `filter(fn)` - Filter array elements
|
|
56
|
-
- `map(fn)` -
|
|
57
|
-
- `return(value)` - Inject a value
|
|
69
|
+
- `map(fn)` - Transform array elements
|
|
70
|
+
- `return(value)` - Inject a new value
|
|
58
71
|
- `each(fn)` - Iterate over array elements
|
|
59
|
-
- `delay(ms)` - Delay
|
|
60
|
-
- `timeout(ms, message?)` -
|
|
61
|
-
- `
|
|
62
|
-
- `
|
|
63
|
-
- `reduce(fn, initialValue?)` - Reduce array
|
|
72
|
+
- `delay(ms)` - Delay resolution
|
|
73
|
+
- `timeout(ms, message?)` - Reject after specified time
|
|
74
|
+
- `props(obj)` - Resolve object properties
|
|
75
|
+
- `tapCatch(fn)` - Execute side effects on rejection
|
|
76
|
+
- `reduce(fn, initialValue?)` - Reduce array elements
|
|
64
77
|
- `throw(reason)` - Return rejected promise
|
|
65
78
|
- `catchThrow(reason)` - Catch and throw new error
|
|
66
79
|
- `catchReturn(value)` - Catch and return value
|
|
67
|
-
- `get(propertyPath)` -
|
|
80
|
+
- `get(propertyPath)` - Retrieve property value
|
|
81
|
+
- `disposer(fn)` - Create a disposer for use with AveAzul.using() for resource cleanup
|
|
68
82
|
|
|
69
83
|
### Static Methods
|
|
70
84
|
|
|
71
|
-
- `
|
|
72
|
-
- `
|
|
73
|
-
- `
|
|
74
|
-
- `
|
|
75
|
-
- `
|
|
76
|
-
- `
|
|
77
|
-
- `
|
|
78
|
-
- `
|
|
79
|
-
- `
|
|
85
|
+
- `delay(ms, value?)` - Resolve after specified time
|
|
86
|
+
- `map(value, fn)` - Transform array elements
|
|
87
|
+
- `try(fn)` - Wrap sync/async functions
|
|
88
|
+
- `props(obj)` - Resolve object properties
|
|
89
|
+
- `defer()` - Create a deferred promise
|
|
90
|
+
- `promisify(fn, options?)` - Convert callback-style functions to promises (preserves original function properties)
|
|
91
|
+
- `each(items, fn)` - Iterate over array elements
|
|
92
|
+
- `reduce(array, fn, initialValue?)` - Reduce array elements
|
|
93
|
+
- `method(fn)` - Creates a method that returns a promise resolving to the value returned by the original function
|
|
94
|
+
- `throw(reason)` - Return rejected promise
|
|
95
|
+
- `promisifyAll(target, options?)` - Convert all methods of an object/class to promises
|
|
96
|
+
- `using(resources, fn)` - Manage resources with automatic cleanup
|
|
97
|
+
|
|
98
|
+
### PromisifyAll Options
|
|
99
|
+
|
|
100
|
+
- `suffix` (default: 'Async') - Suffix to append to promisified method names
|
|
101
|
+
- `filter` - Filter function to determine which methods to promisify
|
|
102
|
+
- `promisifier` - Custom function to handle promisification
|
|
103
|
+
- `multiArgs` (default: false) - Whether to support multiple callback arguments
|
|
104
|
+
|
|
105
|
+
## Development
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# Install dependencies
|
|
109
|
+
npm install
|
|
110
|
+
|
|
111
|
+
# Run tests
|
|
112
|
+
npm test
|
|
113
|
+
npm run test:watch
|
|
114
|
+
npm run test:coverage
|
|
115
|
+
```
|
|
80
116
|
|
|
81
117
|
## License
|
|
82
118
|
|
|
83
|
-
Apache
|
|
119
|
+
Apache-2.0
|
|
84
120
|
|
|
85
|
-
##
|
|
121
|
+
## Author
|
|
86
122
|
|
|
87
|
-
|
|
123
|
+
Joel Chen
|
package/lib/aveazul.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const xaa = require("xaa");
|
|
4
|
-
const { promisify
|
|
5
|
-
|
|
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");
|
|
6
9
|
/**
|
|
7
10
|
* AveAzul ("Blue Bird" in Spanish) - Extended Promise class that provides Bluebird-like utility methods
|
|
8
11
|
* This implementation is inspired by and provides similar APIs to the Bluebird Promise library,
|
|
@@ -28,7 +31,7 @@ class AveAzul extends Promise {
|
|
|
28
31
|
* @returns {Promise} Promise that resolves with the original value
|
|
29
32
|
*/
|
|
30
33
|
tap(fn) {
|
|
31
|
-
return this.then(value => {
|
|
34
|
+
return this.then((value) => {
|
|
32
35
|
fn(value);
|
|
33
36
|
return value;
|
|
34
37
|
});
|
|
@@ -41,7 +44,7 @@ class AveAzul extends Promise {
|
|
|
41
44
|
* @returns {Promise} Promise that resolves with the filtered array
|
|
42
45
|
*/
|
|
43
46
|
filter(fn) {
|
|
44
|
-
return this.then(value => xaa.filter(value, fn));
|
|
47
|
+
return this.then((value) => xaa.filter(value, fn));
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
/**
|
|
@@ -51,7 +54,7 @@ class AveAzul extends Promise {
|
|
|
51
54
|
* @returns {Promise} Promise that resolves with the mapped array
|
|
52
55
|
*/
|
|
53
56
|
map(fn) {
|
|
54
|
-
return this.then(value => xaa.map(value, fn));
|
|
57
|
+
return this.then((value) => xaa.map(value, fn));
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
/**
|
|
@@ -71,7 +74,18 @@ class AveAzul extends Promise {
|
|
|
71
74
|
* @returns {Promise} Promise that resolves when iteration is complete
|
|
72
75
|
*/
|
|
73
76
|
each(fn) {
|
|
74
|
-
return this.then(value =>
|
|
77
|
+
return this.then(async (value) => {
|
|
78
|
+
const result = [];
|
|
79
|
+
for (let i = 0; i < value.length; i++) {
|
|
80
|
+
let x = value[i];
|
|
81
|
+
if (isPromise(x)) {
|
|
82
|
+
x = await x;
|
|
83
|
+
}
|
|
84
|
+
await fn(x, i, value.length);
|
|
85
|
+
result.push(x);
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
});
|
|
75
89
|
}
|
|
76
90
|
|
|
77
91
|
/**
|
|
@@ -80,7 +94,7 @@ class AveAzul extends Promise {
|
|
|
80
94
|
* @returns {Promise} Promise that resolves after the delay
|
|
81
95
|
*/
|
|
82
96
|
delay(ms) {
|
|
83
|
-
return
|
|
97
|
+
return xaa.delay(ms);
|
|
84
98
|
}
|
|
85
99
|
|
|
86
100
|
/**
|
|
@@ -89,43 +103,8 @@ class AveAzul extends Promise {
|
|
|
89
103
|
* @param {string} [message] - Optional error message
|
|
90
104
|
* @returns {Promise} Promise that rejects if timeout occurs
|
|
91
105
|
*/
|
|
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
|
-
});
|
|
106
|
+
timeout(ms, message = "operation timed out") {
|
|
107
|
+
return AveAzul.resolve(xaa.timeout(ms, message).run(this));
|
|
129
108
|
}
|
|
130
109
|
|
|
131
110
|
/**
|
|
@@ -133,34 +112,18 @@ class AveAzul extends Promise {
|
|
|
133
112
|
* @param {Object} obj - Object with promise values
|
|
134
113
|
* @returns {Promise} Promise that resolves with an object of resolved values
|
|
135
114
|
*/
|
|
136
|
-
props(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
115
|
+
props() {
|
|
116
|
+
return this.then((value) => {
|
|
117
|
+
const keys = Object.keys(value);
|
|
118
|
+
const values = keys.map((k) => value[k]);
|
|
119
|
+
|
|
120
|
+
return AveAzul.all(values).then((results) => {
|
|
121
|
+
const resolved = {};
|
|
122
|
+
keys.forEach((k, i) => {
|
|
123
|
+
resolved[k] = results[i];
|
|
124
|
+
});
|
|
125
|
+
return resolved;
|
|
144
126
|
});
|
|
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
127
|
});
|
|
165
128
|
}
|
|
166
129
|
|
|
@@ -170,7 +133,7 @@ class AveAzul extends Promise {
|
|
|
170
133
|
* @returns {Promise} Promise that maintains the rejection
|
|
171
134
|
*/
|
|
172
135
|
tapCatch(fn) {
|
|
173
|
-
return this.catch(err => {
|
|
136
|
+
return this.catch((err) => {
|
|
174
137
|
fn(err);
|
|
175
138
|
throw err;
|
|
176
139
|
});
|
|
@@ -184,7 +147,32 @@ class AveAzul extends Promise {
|
|
|
184
147
|
* @returns {Promise} Promise that resolves with the final reduced value
|
|
185
148
|
*/
|
|
186
149
|
reduce(fn, initialValue) {
|
|
187
|
-
|
|
150
|
+
const hasInitial = arguments.length > 1;
|
|
151
|
+
|
|
152
|
+
return this.then(async (array) => {
|
|
153
|
+
const len = array.length;
|
|
154
|
+
let value;
|
|
155
|
+
let idx;
|
|
156
|
+
if (hasInitial) {
|
|
157
|
+
idx = 0;
|
|
158
|
+
value = initialValue;
|
|
159
|
+
} else {
|
|
160
|
+
idx = 1;
|
|
161
|
+
value = array[0];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
value = isPromise(value) ? await value : value;
|
|
165
|
+
|
|
166
|
+
for (; idx < len; idx++) {
|
|
167
|
+
let x = array[idx];
|
|
168
|
+
if (isPromise(x)) {
|
|
169
|
+
x = await x;
|
|
170
|
+
}
|
|
171
|
+
value = await fn(value, x, idx, len);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return value;
|
|
175
|
+
});
|
|
188
176
|
}
|
|
189
177
|
|
|
190
178
|
/**
|
|
@@ -202,7 +190,9 @@ class AveAzul extends Promise {
|
|
|
202
190
|
* @returns {Promise} Promise that rejects with the new reason
|
|
203
191
|
*/
|
|
204
192
|
catchThrow(reason) {
|
|
205
|
-
return this.catch(() =>
|
|
193
|
+
return this.catch(() => {
|
|
194
|
+
throw reason;
|
|
195
|
+
});
|
|
206
196
|
}
|
|
207
197
|
|
|
208
198
|
/**
|
|
@@ -216,27 +206,23 @@ class AveAzul extends Promise {
|
|
|
216
206
|
|
|
217
207
|
/**
|
|
218
208
|
* Bluebird-style get() for retrieving a property value
|
|
219
|
-
* @param {string|number}
|
|
209
|
+
* @param {string|number} key - Key to retrieve
|
|
220
210
|
* @returns {Promise} Promise that resolves with the property value
|
|
221
211
|
*/
|
|
222
|
-
get(
|
|
223
|
-
return this.then(value =>
|
|
224
|
-
|
|
225
|
-
throw new TypeError("Cannot read property '" + propertyPath + "' of " + value);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
let result = value;
|
|
229
|
-
const props = String(propertyPath).split(".");
|
|
230
|
-
|
|
231
|
-
for (const prop of props) {
|
|
232
|
-
if (result == null) {
|
|
233
|
-
throw new TypeError("Cannot read property '" + prop + "' of " + result);
|
|
234
|
-
}
|
|
235
|
-
result = result[prop];
|
|
236
|
-
}
|
|
212
|
+
get(key) {
|
|
213
|
+
return this.then((value) => value[key]);
|
|
214
|
+
}
|
|
237
215
|
|
|
238
|
-
|
|
239
|
-
|
|
216
|
+
/**
|
|
217
|
+
* Bluebird-style disposer() for resource cleanup
|
|
218
|
+
* @param {Function} fn - Cleanup function
|
|
219
|
+
* @returns {Disposer} Disposer object
|
|
220
|
+
*/
|
|
221
|
+
disposer(fn) {
|
|
222
|
+
if (typeof fn !== "function") {
|
|
223
|
+
throw new TypeError("Expected a function");
|
|
224
|
+
}
|
|
225
|
+
return new Disposer(fn, this);
|
|
240
226
|
}
|
|
241
227
|
}
|
|
242
228
|
|
|
@@ -270,18 +256,18 @@ AveAzul.map = (value, fn) => AveAzul.resolve(xaa.map(value, fn));
|
|
|
270
256
|
* @param {Function} fn - Function to execute
|
|
271
257
|
* @returns {Promise} Promise that resolves with the function's return value
|
|
272
258
|
*/
|
|
273
|
-
AveAzul.try = fn => AveAzul.resolve(xaa.wrap(fn));
|
|
259
|
+
AveAzul.try = (fn) => AveAzul.resolve(xaa.wrap(fn));
|
|
274
260
|
|
|
275
261
|
/**
|
|
276
262
|
* Bluebird-style props() for object properties
|
|
277
263
|
* @param {Object} obj - Object with promise values
|
|
278
264
|
* @returns {Promise} Promise that resolves with an object of resolved values
|
|
279
265
|
*/
|
|
280
|
-
AveAzul.props = obj => {
|
|
266
|
+
AveAzul.props = (obj) => {
|
|
281
267
|
const keys = Object.keys(obj);
|
|
282
|
-
const values = keys.map(k => obj[k]);
|
|
268
|
+
const values = keys.map((k) => obj[k]);
|
|
283
269
|
|
|
284
|
-
return AveAzul.all(values).then(results => {
|
|
270
|
+
return AveAzul.all(values).then((results) => {
|
|
285
271
|
const resolved = {};
|
|
286
272
|
keys.forEach((k, i) => {
|
|
287
273
|
resolved[k] = results[i];
|
|
@@ -295,24 +281,7 @@ AveAzul.props = obj => {
|
|
|
295
281
|
* @returns {Object} Deferred object with promise, resolve, and reject methods
|
|
296
282
|
*/
|
|
297
283
|
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));
|
|
284
|
+
return xaa.makeDefer(AveAzul);
|
|
316
285
|
};
|
|
317
286
|
|
|
318
287
|
/**
|
|
@@ -321,7 +290,9 @@ AveAzul.promisify = (fn, options = {}) => {
|
|
|
321
290
|
* @param {Function} fn - Iterator function to call for each item
|
|
322
291
|
* @returns {Promise} Promise that resolves when iteration is complete
|
|
323
292
|
*/
|
|
324
|
-
AveAzul.each = (items, fn)
|
|
293
|
+
AveAzul.each = function (items, fn) {
|
|
294
|
+
return AveAzul.resolve(items).each(fn);
|
|
295
|
+
};
|
|
325
296
|
|
|
326
297
|
/**
|
|
327
298
|
* Bluebird-style reduce() for array reduction
|
|
@@ -330,124 +301,83 @@ AveAzul.each = (items, fn) => AveAzul.resolve(xaa.each(items, fn));
|
|
|
330
301
|
* @param {*} [initialValue] - Optional initial value
|
|
331
302
|
* @returns {Promise} Promise that resolves with the final reduced value
|
|
332
303
|
*/
|
|
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
|
-
}
|
|
304
|
+
AveAzul.reduce = function (array, ...args) {
|
|
305
|
+
return AveAzul.resolve(array).reduce(...args);
|
|
306
|
+
};
|
|
344
307
|
|
|
345
|
-
|
|
308
|
+
/**
|
|
309
|
+
* Bluebird-style promisify() for converting callback-based functions to promises
|
|
310
|
+
* @param {Function} fn - Function to promisify
|
|
311
|
+
* @param {Object} [options] - Options object
|
|
312
|
+
* @returns {Function} Promisified function
|
|
313
|
+
*/
|
|
314
|
+
AveAzul.promisify = (fn, options) => {
|
|
315
|
+
return promisify(fn, {
|
|
316
|
+
...options,
|
|
317
|
+
Promise: AveAzul,
|
|
346
318
|
});
|
|
347
319
|
};
|
|
348
320
|
|
|
349
321
|
/**
|
|
350
|
-
* Bluebird-style
|
|
351
|
-
* @param {
|
|
352
|
-
* @
|
|
322
|
+
* Bluebird-style promisifyAll() for converting callback-based functions to promises
|
|
323
|
+
* @param {Object} target - Object to promisify
|
|
324
|
+
* @param {Object} [options] - Options object
|
|
325
|
+
* @returns {Object} Object with promisified methods
|
|
353
326
|
*/
|
|
354
|
-
AveAzul.
|
|
327
|
+
AveAzul.promisifyAll = (target, options) => {
|
|
328
|
+
return promisifyAll(target, { ...options, Promise: AveAzul });
|
|
329
|
+
};
|
|
355
330
|
|
|
356
331
|
/**
|
|
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();
|
|
332
|
+
* Bluebird-style method() for creating a method that returns a promise
|
|
333
|
+
* @param {Function} fn - Function to create a method for
|
|
334
|
+
* @returns {Function} Method function that returns a promise
|
|
399
335
|
*/
|
|
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
|
-
};
|
|
336
|
+
AveAzul.method = (fn) => {
|
|
337
|
+
return function (...args) {
|
|
338
|
+
return new AveAzul((resolve, reject) => {
|
|
339
|
+
try {
|
|
340
|
+
const result = fn.call(this, ...args);
|
|
341
|
+
resolve(result);
|
|
342
|
+
} catch (error) {
|
|
343
|
+
reject(error);
|
|
422
344
|
}
|
|
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
|
-
}
|
|
433
|
-
|
|
434
|
-
const targetObj = target.prototype || target;
|
|
435
|
-
const keys = Object.getOwnPropertyNames(targetObj);
|
|
345
|
+
});
|
|
346
|
+
};
|
|
347
|
+
};
|
|
436
348
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
349
|
+
/**
|
|
350
|
+
* Bluebird-style using() for resource management. There is only a static version of this method.
|
|
351
|
+
* After the handler finish and returns, regardless of whether it resolves or rejects, the resources will be disposed.
|
|
352
|
+
*
|
|
353
|
+
* @param {Disposer|Array<Disposer>} resources - Resource disposers, either an array of disposers or a variadic argument list
|
|
354
|
+
* @param {Function} handler - Handler function that will receive the resources as arguments
|
|
355
|
+
* @returns {Promise} Promise that resolves with handler result
|
|
356
|
+
*/
|
|
357
|
+
AveAzul.using = (resources, ...args) => {
|
|
358
|
+
if (args.length === 0) {
|
|
359
|
+
throw new TypeError("resrouces and handler function required");
|
|
443
360
|
}
|
|
444
361
|
|
|
445
|
-
if (
|
|
446
|
-
|
|
447
|
-
|
|
362
|
+
if (Array.isArray(resources)) {
|
|
363
|
+
if (args.length > 1) {
|
|
364
|
+
throw new TypeError(
|
|
365
|
+
"only two arguments are allowed when passing an array of resources"
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
return using(resources, args[0], AveAzul, true);
|
|
448
369
|
}
|
|
449
|
-
|
|
450
|
-
return
|
|
370
|
+
const handler = args.pop();
|
|
371
|
+
return using([resources, ...args], handler, AveAzul, false);
|
|
451
372
|
};
|
|
452
373
|
|
|
374
|
+
/**
|
|
375
|
+
* @description
|
|
376
|
+
* When fatal error and AveAzul needs to crash the process,
|
|
377
|
+
* this method is used to throw the error.
|
|
378
|
+
*
|
|
379
|
+
* @param {Error} error - The error to throw.
|
|
380
|
+
*/
|
|
381
|
+
AveAzul.___throwUncaughtError = triggerUncaughtException;
|
|
382
|
+
|
|
453
383
|
module.exports = 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(data, promise) {
|
|
9
|
+
this._data = data; // 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,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { promisify } = require("./promisify");
|
|
4
|
+
const {
|
|
5
|
+
isIdentifier,
|
|
6
|
+
isClass,
|
|
7
|
+
isConstructor,
|
|
8
|
+
isPromisified,
|
|
9
|
+
getObjectKeys,
|
|
10
|
+
} = require("./util");
|
|
11
|
+
|
|
12
|
+
const defaultSuffix = "Async";
|
|
13
|
+
|
|
14
|
+
const defaultFilter = function (name) {
|
|
15
|
+
return isIdentifier(name) && name.charAt(0) !== "_" && name !== "constructor";
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const defaultPromisifier = (fn, _defaultPromisifier, options) => {
|
|
19
|
+
return promisify(fn, {
|
|
20
|
+
...options,
|
|
21
|
+
copyProps: false,
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const excludedPrototypes = [
|
|
26
|
+
Object.getPrototypeOf(Array),
|
|
27
|
+
Object.getPrototypeOf(Object),
|
|
28
|
+
Object.getPrototypeOf(Function),
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const excludedClasses = [Array, Object, Function];
|
|
32
|
+
|
|
33
|
+
// Helper function to determine if a class extends from any excluded class
|
|
34
|
+
function isExcludedClass(obj) {
|
|
35
|
+
if (excludedClasses.includes(obj)) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Check if obj extends from any excluded class using instanceof
|
|
40
|
+
if (typeof obj === "function" && obj.prototype) {
|
|
41
|
+
// Check if prototype is instance of any excluded class
|
|
42
|
+
for (const excludedClass of excludedClasses) {
|
|
43
|
+
if (obj.prototype instanceof excludedClass) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function promisifyAll2(obj, options) {
|
|
53
|
+
if (isExcludedClass(obj)) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const allKeys = getObjectKeys(obj, excludedPrototypes);
|
|
58
|
+
|
|
59
|
+
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
|
+
const value = obj[key];
|
|
69
|
+
const promisifiedKey = key + options.suffix;
|
|
70
|
+
const passesDefaultFilter =
|
|
71
|
+
options.filter === defaultFilter ? true : defaultFilter(key, value, obj);
|
|
72
|
+
if (
|
|
73
|
+
isConstructor(value) ||
|
|
74
|
+
typeof value !== "function" ||
|
|
75
|
+
isPromisified(value) ||
|
|
76
|
+
obj[promisifiedKey] ||
|
|
77
|
+
!options.filter(key, value, obj, passesDefaultFilter)
|
|
78
|
+
) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
obj[promisifiedKey] = options.promisifier(value, defaultPromisifier, {
|
|
82
|
+
context: obj,
|
|
83
|
+
copyProps: false,
|
|
84
|
+
multiArgs: options.multiArgs,
|
|
85
|
+
Promise: options.Promise,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function promisifyAll(target, _options) {
|
|
91
|
+
if (typeof target !== "function" && typeof target !== "object") {
|
|
92
|
+
throw new TypeError(
|
|
93
|
+
"the target of promisifyAll must be an object or a function"
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const options = {
|
|
98
|
+
suffix: defaultSuffix,
|
|
99
|
+
filter: defaultFilter,
|
|
100
|
+
promisifier: defaultPromisifier,
|
|
101
|
+
Promise: global.Promise,
|
|
102
|
+
..._options,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const suffix = options.suffix;
|
|
106
|
+
|
|
107
|
+
if (!isIdentifier(suffix)) {
|
|
108
|
+
throw new RangeError(
|
|
109
|
+
"suffix must be a valid identifier\u000a\u000a See http://goo.gl/MqrFmX\u000a"
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const allKeys = getObjectKeys(target, excludedPrototypes);
|
|
114
|
+
|
|
115
|
+
for (const key of allKeys) {
|
|
116
|
+
const value = target[key];
|
|
117
|
+
if (
|
|
118
|
+
value &&
|
|
119
|
+
key !== "constructor" &&
|
|
120
|
+
!key.startsWith("_") &&
|
|
121
|
+
isClass(value)
|
|
122
|
+
) {
|
|
123
|
+
const proto = Object.getPrototypeOf(value);
|
|
124
|
+
if (!excludedPrototypes.includes(proto)) {
|
|
125
|
+
promisifyAll2(proto, options);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
promisifyAll2(value, options);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
promisifyAll2(target, options);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
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,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { Disposer } = require("./disposer");
|
|
4
|
+
const { isPromise } = require("./util");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @description
|
|
8
|
+
* The using function is a utility function that allows you to acquire resources,
|
|
9
|
+
* process them, and then dispose of them in an error-safe manner.
|
|
10
|
+
*
|
|
11
|
+
* @param {Array} resources - An array of resources to acquire.
|
|
12
|
+
* @param {Function} handler - A function that will be called with the acquired resources.
|
|
13
|
+
* @param {Promise} Promise - The Promise implementation to use. AveAzul or Bluebird.
|
|
14
|
+
* @param {boolean} asArray - Whether to return the result as an array.
|
|
15
|
+
* @returns {Promise} A promise that resolves to the result of the handler function.
|
|
16
|
+
*/
|
|
17
|
+
function using(resources, handler, Promise, asArray) {
|
|
18
|
+
if (typeof handler !== "function") {
|
|
19
|
+
throw new TypeError("handler must be a function");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// resources is guaranateed to be an array of disposer, promise like, or any value
|
|
23
|
+
// first process all resources by mapping the resources array:
|
|
24
|
+
// 1. if it's a disposer, get its promise and resolve its value
|
|
25
|
+
// 2. if it's a promise like, get its value
|
|
26
|
+
// 3. otherwise, return the value
|
|
27
|
+
// Expect Promise to be AveAzul or Bluebird that has map method
|
|
28
|
+
const acquisitionErrors = [];
|
|
29
|
+
|
|
30
|
+
const acquireResources = () => {
|
|
31
|
+
const promiseRes = resources.map((resource) => {
|
|
32
|
+
// if it's a promise-like, wait for its resolved value
|
|
33
|
+
if (isPromise(resource)) {
|
|
34
|
+
return { ___promise: resource };
|
|
35
|
+
}
|
|
36
|
+
return resource;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return Promise.map(promiseRes, async (resource) => {
|
|
40
|
+
if (resource instanceof Disposer) {
|
|
41
|
+
try {
|
|
42
|
+
const res = await resource._promise;
|
|
43
|
+
resource._result = res;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
acquisitionErrors.push(error);
|
|
46
|
+
resource._error = error;
|
|
47
|
+
}
|
|
48
|
+
return resource;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// if it's a promise like, wait for its resolved value
|
|
52
|
+
if (resource && resource.___promise) {
|
|
53
|
+
try {
|
|
54
|
+
const res = await resource.___promise;
|
|
55
|
+
resource._result = res;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
acquisitionErrors.push(error);
|
|
58
|
+
resource._error = error;
|
|
59
|
+
}
|
|
60
|
+
return resource;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { _result: resource };
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const disposeResources = (processedResources) => {
|
|
68
|
+
const errors = [];
|
|
69
|
+
return Promise.each(processedResources, async (resource) => {
|
|
70
|
+
// 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
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}).finally(() => {
|
|
81
|
+
if (errors.length > 0) {
|
|
82
|
+
Promise.___throwUncaughtError(new Error("cleanup resources failed"));
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
return acquireResources().then((processedResources) => {
|
|
88
|
+
if (acquisitionErrors.length > 0) {
|
|
89
|
+
return disposeResources(processedResources).tap(() => {
|
|
90
|
+
throw acquisitionErrors[0];
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// now collect all the results into an array
|
|
95
|
+
const results = [];
|
|
96
|
+
for (const resource of processedResources) {
|
|
97
|
+
results.push(resource._result);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let handlerResult;
|
|
101
|
+
let handlerSyncError;
|
|
102
|
+
try {
|
|
103
|
+
// now call the handler with the results
|
|
104
|
+
if (asArray) {
|
|
105
|
+
handlerResult = handler(results);
|
|
106
|
+
} else {
|
|
107
|
+
handlerResult = handler(...results);
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
// catch sync error from handler
|
|
111
|
+
handlerSyncError = error;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return disposeResources(processedResources).then(() => {
|
|
115
|
+
if (handlerSyncError) {
|
|
116
|
+
throw handlerSyncError;
|
|
117
|
+
}
|
|
118
|
+
return handlerResult;
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports.using = using;
|
package/lib/util.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
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
|
+
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
|
+
try {
|
|
19
|
+
const fnStr = fn.toString();
|
|
20
|
+
if (fnStr.startsWith("class ") || /^class\s+/.test(fnStr)) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
} catch (e) {
|
|
24
|
+
// Ignore errors that might occur when calling toString()
|
|
25
|
+
// Some objects might have custom toString implementations that throw
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
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
|
+
}
|
|
50
|
+
|
|
51
|
+
const rident = /^[a-z$_][a-z$_0-9]*$/i;
|
|
52
|
+
function isIdentifier(str) {
|
|
53
|
+
return rident.test(str);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isConstructor(func) {
|
|
57
|
+
if (!func) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
const proto = func.prototype;
|
|
61
|
+
return (
|
|
62
|
+
!!proto &&
|
|
63
|
+
!!proto.constructor &&
|
|
64
|
+
!!proto.constructor.name &&
|
|
65
|
+
proto.constructor.name === func.name
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Prop filtering code copied from bluebird/js/release
|
|
71
|
+
*/
|
|
72
|
+
const noCopyProps = [
|
|
73
|
+
"arity",
|
|
74
|
+
"length",
|
|
75
|
+
"name",
|
|
76
|
+
"arguments",
|
|
77
|
+
"caller",
|
|
78
|
+
"callee",
|
|
79
|
+
"prototype",
|
|
80
|
+
"__isPromisified__",
|
|
81
|
+
];
|
|
82
|
+
const noCopyPropsPattern = new RegExp("^(?:" + noCopyProps.join("|") + ")$");
|
|
83
|
+
|
|
84
|
+
function propsFilter(key) {
|
|
85
|
+
return !noCopyPropsPattern.test(key);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function copyOwnProperties(source, target, filter = propsFilter) {
|
|
89
|
+
const names = Object.getOwnPropertyNames(source);
|
|
90
|
+
|
|
91
|
+
for (const name of names) {
|
|
92
|
+
if (filter(name)) {
|
|
93
|
+
Object.defineProperty(
|
|
94
|
+
target,
|
|
95
|
+
name,
|
|
96
|
+
Object.getOwnPropertyDescriptor(source, name)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Copied from bluebird/js/release/util.js
|
|
104
|
+
* @param {*} fn
|
|
105
|
+
* @returns {boolean}
|
|
106
|
+
*/
|
|
107
|
+
function isPromisified(fn) {
|
|
108
|
+
try {
|
|
109
|
+
return fn.__isPromisified__ === true;
|
|
110
|
+
} catch (e) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Determines if an object is a Promise instance
|
|
117
|
+
* @param {*} obj - The object to check
|
|
118
|
+
* @returns {boolean} - True if the object is a Promise instance, false otherwise
|
|
119
|
+
*/
|
|
120
|
+
function isPromise(obj) {
|
|
121
|
+
return (
|
|
122
|
+
obj instanceof Promise ||
|
|
123
|
+
(obj != null &&
|
|
124
|
+
typeof obj === "object" &&
|
|
125
|
+
typeof obj.then === "function" &&
|
|
126
|
+
typeof obj.catch === "function")
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const defaultExcluded = [
|
|
131
|
+
Object.getPrototypeOf(Array), // Array.prototype
|
|
132
|
+
Object.getPrototypeOf(Object), // Object.prototype
|
|
133
|
+
Object.getPrototypeOf(Function), // Function.prototype
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Gets all property keys from an object and its prototype chain, excluding standard
|
|
138
|
+
* prototypes like Object.prototype, Array.prototype, and Function.prototype
|
|
139
|
+
*
|
|
140
|
+
* @param {Object} target - The target object to get keys from
|
|
141
|
+
* @param {Array} [excludedPrototypes=[]] - An array of prototype objects to exclude keys from
|
|
142
|
+
* @returns {Array<string>} - Array of property keys
|
|
143
|
+
*/
|
|
144
|
+
function getObjectKeys(target, excludedPrototypes = []) {
|
|
145
|
+
const excluded =
|
|
146
|
+
excludedPrototypes.length > 0 ? excludedPrototypes : defaultExcluded;
|
|
147
|
+
|
|
148
|
+
// Get own properties
|
|
149
|
+
const ownKeys = Object.getOwnPropertyNames(target);
|
|
150
|
+
|
|
151
|
+
// Get prototype properties, excluding those from excluded prototypes
|
|
152
|
+
let protoKeys = [];
|
|
153
|
+
let currentProto = Object.getPrototypeOf(target);
|
|
154
|
+
|
|
155
|
+
// Walk up the prototype chain until we hit null or an excluded prototype
|
|
156
|
+
while (currentProto && !excluded.includes(currentProto)) {
|
|
157
|
+
protoKeys = [...protoKeys, ...Object.getOwnPropertyNames(currentProto)];
|
|
158
|
+
currentProto = Object.getPrototypeOf(currentProto);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Combine own properties and prototype properties
|
|
162
|
+
return [...protoKeys, ...ownKeys];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Triggers an uncaught exception in a safe way by scheduling it on the next event loop tick
|
|
167
|
+
* This is used for fatal errors that should crash the process
|
|
168
|
+
* @param {Error} error - The error to throw
|
|
169
|
+
*/
|
|
170
|
+
function triggerUncaughtException(error) {
|
|
171
|
+
if (!(error instanceof Error)) {
|
|
172
|
+
error = new Error(String(error));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Use setTimeout with 0ms delay to throw on the next event loop tick
|
|
176
|
+
// This ensures the current execution context completes first
|
|
177
|
+
setTimeout(() => {
|
|
178
|
+
throw error;
|
|
179
|
+
}, 0);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
module.exports.copyOwnProperties = copyOwnProperties;
|
|
183
|
+
module.exports.isClass = isClass;
|
|
184
|
+
module.exports.isIdentifier = isIdentifier;
|
|
185
|
+
module.exports.isConstructor = isConstructor;
|
|
186
|
+
module.exports.isPromisified = isPromisified;
|
|
187
|
+
module.exports.isPromise = isPromise;
|
|
188
|
+
module.exports.triggerUncaughtException = triggerUncaughtException;
|
|
189
|
+
module.exports.getObjectKeys = getObjectKeys;
|
package/package.json
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aveazul",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "Bluebird-like APIs in extended native Promise",
|
|
5
5
|
"main": "lib/aveazul.js",
|
|
6
6
|
"homepage": "https://github.com/jchip/aveazul",
|
|
7
7
|
"license": "Apache-2.0",
|
|
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"
|
|
12
15
|
},
|
|
13
16
|
"author": "Joel Chen",
|
|
14
17
|
"files": [
|
|
@@ -28,6 +31,8 @@
|
|
|
28
31
|
"xaa": "^1.7.3"
|
|
29
32
|
},
|
|
30
33
|
"devDependencies": {
|
|
31
|
-
"
|
|
34
|
+
"bluebird": "^3.7.2",
|
|
35
|
+
"jest": "^29.7.0",
|
|
36
|
+
"rimraf": "^3.0.1"
|
|
32
37
|
}
|
|
33
38
|
}
|