@@ -10,6 +10,28 @@ module.exports = {
1010 getRuntimeObject : getObject // TODO: Called once per stack frame, but doesn't retain the `deadlineReached` flag.
1111}
1212
13+ /**
14+ * @typedef {Object } GetObjectOptions
15+ * @property {Object } maxReferenceDepth - The maximum depth of the object to traverse
16+ * @property {number } maxCollectionSize - The maximum size of a collection to include in the snapshot
17+ * @property {number } maxFieldCount - The maximum number of properties on an object to include in the snapshot
18+ * @property {bigint } deadlineNs - The deadline in nanoseconds compared to `process.hrtime.bigint()`
19+ * @property {boolean } [deadlineReached=false] - Whether the deadline has been reached. Should not be set by the
20+ * caller, but is used to signal the deadline overrun to the caller.
21+ */
22+
23+ /**
24+ * Get the properties of an object using the Chrome DevTools Protocol.
25+ *
26+ * @param {string } objectId - The ID of the object to get the properties of
27+ * @param {GetObjectOptions } opts - The options for the snapshot. Also used to track the deadline and communicate the
28+ * deadline overrun to the caller using the `deadlineReached` flag.
29+ * @param {number } [depth=0] - The depth of the object. Only used internally by this module to track the current depth
30+ * and should not be set by the caller.
31+ * @param {boolean } [collection=false] - Whether the object is a collection. Only used internally by this module to
32+ * track the current object type and should not be set by the caller.
33+ * @returns {Promise<Object[]> } The properties of the object
34+ */
1335async function getObject ( objectId , opts , depth = 0 , collection = false ) {
1436 const { result, privateProperties } = await session . post ( 'Runtime.getProperties' , {
1537 objectId,
@@ -43,30 +65,48 @@ async function traverseGetPropertiesResult (props, opts, depth) {
4365
4466 if ( depth >= opts . maxReferenceDepth ) return props
4567
46- const promises = [ ]
68+ const work = [ ]
4769
4870 for ( const prop of props ) {
4971 if ( prop . value === undefined ) continue
50- if ( overBudget ( opts ) ) {
51- prop . value [ timeBudgetSym ] = true
52- continue
53- }
5472 const { value : { type, objectId, subtype } } = prop
5573 if ( type === 'object' ) {
5674 if ( objectId === undefined ) continue // if `subtype` is "null"
5775 if ( LEAF_SUBTYPES . has ( subtype ) ) continue // don't waste time with these subtypes
58- promises . push ( getObjectProperties ( subtype , objectId , opts , depth ) . then ( ( properties ) => {
59- prop . value . properties = properties
60- } ) )
76+ work . push ( [
77+ prop . value ,
78+ ( ) => getObjectProperties ( subtype , objectId , opts , depth ) . then ( ( properties ) => {
79+ prop . value . properties = properties
80+ } )
81+ ] )
6182 } else if ( type === 'function' ) {
62- promises . push ( getFunctionProperties ( objectId , opts , depth + 1 ) . then ( ( properties ) => {
63- prop . value . properties = properties
64- } ) )
83+ work . push ( [
84+ prop . value ,
85+ ( ) => getFunctionProperties ( objectId , opts , depth + 1 ) . then ( ( properties ) => {
86+ prop . value . properties = properties
87+ } )
88+ ] )
6589 }
6690 }
6791
68- if ( promises . length ) {
69- await Promise . all ( promises )
92+ if ( work . length ) {
93+ // Iterate over the work in chunks of 2. The closer to 1, the less we'll overshoot the deadline, but the longer it
94+ // takes to complete. `2` seems to be the best compromise.
95+ // Anecdotally, on my machine, with no deadline, a concurrency of `1` takes twice as long as a concurrency of `2`.
96+ // From thereon, there's no real measureable savings with a higher concurrency.
97+ for ( let i = 0 ; i < work . length ; i += 2 ) {
98+ if ( overBudget ( opts ) ) {
99+ for ( let j = i ; j < work . length ; j ++ ) {
100+ work [ j ] [ 0 ] [ timeBudgetSym ] = true
101+ }
102+ break
103+ }
104+ // eslint-disable-next-line no-await-in-loop
105+ await Promise . all ( [
106+ work [ i ] [ 1 ] ( ) ,
107+ work [ i + 1 ] ?. [ 1 ] ( )
108+ ] )
109+ }
70110 }
71111
72112 return props
0 commit comments