-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathutils.js
175 lines (163 loc) · 6.27 KB
/
utils.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// Wraps an existing iterator and proxies it's .next(). But, adds a .peek() method that lets you look ahead
// and see if there are any more values before you act on them. It allows you to make logic decisions
// based on whether there are any more values before you've permanently fetched the value.
// peek() actually causes the value to be retrieved from the original iterator when it is called so if there
// are side effects from retrieving a value from the original iterator, those side effecdts will occur when peek()
// is called, not when next() is called.
// You can call peek() as many times as you want on the same value. Once it has retrieved the value from
// the original iterator, it caches that value until you call next() to fetch it. You MUST call next() to
// be able to advance the iterator. Repeated calls to peek() will just return the same value over and over.
function getPeekIterator(iterator) {
// three states: "noValueCached", "valueCached", "done"
let state = "noValueCached";
let value;
let newIterator = {};
// retrieve next value without advanced iterator
newIterator.peek = function() {
if (state === "done") return {done: true};
if (state === "valueCached") return value;
// state must be "noValueCached"
value = iterator.next();
state = value.done ? "done" : "valueCached";
return value;
}
// retrieve next value, advance iterator
newIterator.next = function() {
let nextVal = newIterator.peek();
if (!nextVal.done) {
// by putting the state to noValueCached, we "take" the value out of the cache
// so the next time peek() is called it will get a new value
state = "noValueCached";
}
return nextVal;
}
newIterator.nextValue = function() {
let nextVal = newIterator.next();
if (nextVal.done) {
throw new Error("No more values");
} else {
return nextVal.value;
}
}
// simple boolean wrapper around .peek()
// returns true if there's more data, returns false is there's no more data
newIterator.isMore = function() {
let val = newIterator.peek();
return !val.done;
}
return newIterator;
}
// Pass an iterable (like an Array or Set or Map) and it will get the default iterator
// and pass that to getPeekIterator() for you to get a peekIterator for that default iterator
function getDefaultPeekIterator(iterable) {
return getPeekIterator(iterable[Symbol.iterator]());
}
// Use promiseAllDone() when you want two possible outcomes:
// 1) If all promises resolve, you get an array of simple results (same output as Promise.all())
// 2) If any promise rejects, you get the first rejection as a rejected promise, but you don't
// get the rejection until all the promises are completed in some way.
// So, it differs from Promise.all() in that it does not finish until all the promises have
// finished, even if there's a rejection
// It differs from Promise.allSettled() in two ways. First, it will reject if any promises
// reject whereas Promise.allSettled() never rejects. Second, when it resolves, it
// resolves to a simple array of results, not an arrray of objects.
function allDone(promises) {
return Promise.allSettled(promises).then(results => {
return results.map(r => {
if (r.status === 'rejected') {
// reject with first error we found
throw r.reason;
}
return r.value;
});
});
}
// Like Promise.allSettled, but instead of resolving to an array of objects that have
// the status in it, the array is just the resolved values with a sentinel errorVal
// in place of the errors (often null)
// returns promise that resolves to an array
function settleWithVal(promises, errorVal = null) {
return Promise.allSettled(promises).then(results => {
return results.map(result => {
return result.status === 'fulfilled' ? result.value : errorVal;
})
});
}
// Used to add a timeout to a promise, will reject
// if timeout reached before promise resolves
// promiseTimeout(p).then(...).catch(...)
// promiseTimeout(promise, timeMs, [error object])
// If error object not passed, then new Error("Timeout"); will be used
// Returns promise
function timeout(p, t, e) {
let timer;
const pTimeout = new Promise((resolve, reject) => {
timer = setTimeout(() => {
if (!e) {
e = new Error("Timeout");
}
timer = null;
reject(e);
}, t);
});
return Promise.race([p, pTimeout]).finally(() => {
// don't leave timer running if it wasn't used
// so process can shut-down automatically, GC can run sooner, etc...
if (timer) {
clearTimeout(timer);
}
});
}
function delay(t, val) {
return new Promise(function(resolve) {
setTimeout(resolve, t, val);
});
}
function delayErr(t, err) {
return delay(t).then(() => {
if (!(err instanceof Error)) {
throw new Error(err);
} else {
throw err;
}
});
}
// promisifyAll implemented using util.promisify
const promisify = require('util').promisify;
function promisifyObj(obj, suffix = "Async") {
const type = typeof obj;
if (!(type === "function" || type === "object")) {
throw new Error("first argument to promisifyObj() must be function or object");
}
if (typeof suffix !== "string") {
throw new Error("second argument to promisifyObj() must be a string");
}
Object.getOwnPropertyNames(obj).filter(prop => {
// filter out non-function properties
return typeof obj[prop] === "function";
}).forEach(method => {
const asyncName = method + suffix;
if (!(asyncName in obj)) {
obj[asyncName] = promisify(obj[method]);
}
});
return obj;
}
function promisifyAll(obj, suffix = "Async") {
promisifyObj(obj, suffix);
if (typeof obj === "function" && typeof obj.prototype === "object" ) {
promisifyObj(obj.prototype, suffix);
}
return obj;
}
module.exports = {
settleWithVal,
timeout,
allDone,
delay,
delayErr,
promisifyAll,
promisifyObj,
getPeekIterator,
getDefaultPeekIterator,
};