1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 * General utilities used throughout devtools that can also be used in
13 * Immutably reduce the given `...objs` into one object. The reduction is
14 * applied from left to right, so `immutableUpdate({ a: 1 }, { a: 2 })` will
15 * result in `{ a: 2 }`. The resulting object is frozen.
19 * const original = { foo: 1, bar: 2, baz: 3 };
20 * const modified = immutableUpdate(original, { baz: 0, bang: 4 });
22 * // We get the new object that we expect...
23 * assert(modified.baz === 0);
24 * assert(modified.bang === 4);
26 * // However, the original is not modified.
27 * assert(original.baz === 2);
28 * assert(original.bang === undefined);
30 * @param {...Object} ...objs
33 exports
.immutableUpdate = function (...objs
) {
34 return Object
.freeze(Object
.assign({}, ...objs
));
38 * Utility function for updating an object with the properties of
41 * DEPRECATED: Just use Object.assign() instead!
43 * @param aTarget Object
44 * The object being updated.
45 * @param aNewAttrs Object
46 * The rest params are objects to update aTarget with. You
47 * can pass as many as you like.
49 exports
.update
= function update(target
, ...args
) {
50 for (const attrs
of args
) {
51 for (const key
in attrs
) {
52 const desc
= Object
.getOwnPropertyDescriptor(attrs
, key
);
55 Object
.defineProperty(target
, key
, desc
);
63 * Utility function for getting the values from an object as an array
65 * @param object Object
66 * The object to iterate over
68 exports
.values
= function values(object
) {
69 return Object
.keys(object
).map(k
=> object
[k
]);
73 * Report that |who| threw an exception, |exception|.
75 exports
.reportException
= function reportException(who
, exception
) {
76 const msg
= `${who} threw an exception: ${exports.safeErrorString(
81 if (typeof console
!== "undefined" && console
&& console
.error
) {
82 console
.error(exception
);
87 * Given a handler function that may throw, return an infallible handler
88 * function that calls the fallible handler, and logs any exceptions it
91 * @param handler function
92 * A handler function, which may throw.
94 * A name for handler, for use in error messages. If omitted, we use
97 * (SpiderMonkey does generate good names for anonymous functions, but we
98 * don't have a way to get at them from JavaScript at the moment.)
100 exports
.makeInfallible = function (handler
, name
= handler
.name
) {
103 return handler
.apply(this, arguments
);
105 let who
= "Handler function";
109 exports
.reportException(who
, ex
);
116 * Turn the |error| into a string, without fail.
118 * @param {Error|any} error
120 exports
.safeErrorString = function (error
) {
122 let errorString
= error
.toString();
123 if (typeof errorString
== "string") {
124 // Attempt to attach a stack to |errorString|. If it throws an error, or
125 // isn't a string, don't use it.
128 const stack
= error
.stack
.toString();
129 if (typeof stack
== "string") {
130 errorString
+= "\nStack: " + stack
;
137 // Append additional line and column number information to the output,
138 // since it might not be part of the stringified error.
140 typeof error
.lineNumber
== "number" &&
141 typeof error
.columnNumber
== "number"
144 "Line: " + error
.lineNumber
+ ", column: " + error
.columnNumber
;
153 // We failed to find a good error description, so do the next best thing.
154 return Object
.prototype.toString
.call(error
);
158 * Interleaves two arrays element by element, returning the combined array, like
159 * a zip. In the case of arrays with different sizes, undefined values will be
160 * interleaved at the end along with the extra values of the larger array.
165 * The combined array, in the form [a1, b1, a2, b2, ...]
167 exports
.zip = function (a
, b
) {
176 let i
= 0, aLength
= a
.length
, bLength
= b
.length
;
177 i
< aLength
|| i
< bLength
;
180 pairs
.push([a
[i
], b
[i
]]);
186 * Converts an object into an array with 2-element arrays as key/value
187 * pairs of the object. `{ foo: 1, bar: 2}` would become
188 * `[[foo, 1], [bar 2]]` (order not guaranteed).
193 exports
.entries
= function entries(obj
) {
194 return Object
.keys(obj
).map(k
=> [k
, obj
[k
]]);
198 * Takes an array of 2-element arrays as key/values pairs and
199 * constructs an object using them.
201 exports
.toObject = function (arr
) {
203 for (const [k
, v
] of arr
) {
210 * Composes the given functions into a single function, which will
211 * apply the results of each function right-to-left, starting with
212 * applying the given arguments to the right-most function.
213 * `compose(foo, bar, baz)` === `args => foo(bar(baz(args)))`
215 * @param ...function funcs
218 exports
.compose
= function compose(...funcs
) {
219 return (...args
) => {
220 const initialValue
= funcs
[funcs
.length
- 1](...args
);
221 const leftFuncs
= funcs
.slice(0, -1);
222 return leftFuncs
.reduceRight((composed
, f
) => f(composed
), initialValue
);
227 * Return true if `thing` is a generator function, false otherwise.
229 exports
.isGenerator = function (fn
) {
230 if (typeof fn
!== "function") {
233 const proto
= Object
.getPrototypeOf(fn
);
237 const ctor
= proto
.constructor;
241 return ctor
.name
== "GeneratorFunction";
245 * Return true if `thing` is an async function, false otherwise.
247 exports
.isAsyncFunction = function (fn
) {
248 if (typeof fn
!== "function") {
251 const proto
= Object
.getPrototypeOf(fn
);
255 const ctor
= proto
.constructor;
259 return ctor
.name
== "AsyncFunction";
263 * Return true if `thing` is a Promise or then-able, false otherwise.
265 exports
.isPromise = function (p
) {
266 return p
&& typeof p
.then
=== "function";
270 * Return true if `thing` is a SavedFrame, false otherwise.
272 exports
.isSavedFrame = function (thing
) {
273 return Object
.prototype.toString
.call(thing
) === "[object SavedFrame]";
277 * Return true iff `thing` is a `Set` object (possibly from another global).
279 exports
.isSet = function (thing
) {
280 return Object
.prototype.toString
.call(thing
) === "[object Set]";
284 * Given a list of lists, flatten it. Only flattens one level; does not
285 * recursively flatten all levels.
287 * @param {Array<Array<Any>>} lists
288 * @return {Array<Any>}
290 exports
.flatten = function (lists
) {
291 return Array
.prototype.concat
.apply([], lists
);
295 * Returns a promise that is resolved or rejected when all promises have settled
296 * (resolved or rejected).
298 * This differs from Promise.all, which will reject immediately after the first
299 * rejection, instead of waiting for the remaining promises to settle.
302 * Iterable of promises that may be pending, resolved, or rejected. When
303 * when all promises have settled (resolved or rejected), the returned
304 * promise will be resolved or rejected as well.
306 * @return A new promise that is fulfilled when all values have settled
307 * (resolved or rejected). Its resolution value will be an array of all
308 * resolved values in the given order, or undefined if values is an
309 * empty array. The reject reason will be forwarded from the first
310 * promise in the list of given promises to be rejected.
312 exports
.settleAll
= values
=> {
313 if (values
=== null || typeof values
[Symbol
.iterator
] != "function") {
314 throw new Error("settleAll() expects an iterable.");
317 return new Promise((resolve
, reject
) => {
318 values
= Array
.isArray(values
) ? values
: [...values
];
319 let countdown
= values
.length
;
320 const resolutionValues
= new Array(countdown
);
322 let rejectionOccurred
= false;
325 resolve(resolutionValues
);
329 function checkForCompletion() {
330 if (--countdown
> 0) {
333 if (!rejectionOccurred
) {
334 resolve(resolutionValues
);
336 reject(rejectionValue
);
340 for (let i
= 0; i
< values
.length
; i
++) {
342 const value
= values
[i
];
343 const resolver
= result
=> {
344 resolutionValues
[index
] = result
;
345 checkForCompletion();
347 const rejecter
= error
=> {
348 if (!rejectionOccurred
) {
349 rejectionValue
= error
;
350 rejectionOccurred
= true;
352 checkForCompletion();
355 if (value
&& typeof value
.then
== "function") {
356 value
.then(resolver
, rejecter
);
358 // Given value is not a promise, forward it as a resolution value.