@@ -77,20 +77,30 @@ class HookCodeFactory {
7777 }
7878
7979 setup ( instance , options ) {
80- instance . _x = options . taps . map ( ( t ) => t . fn ) ;
80+ const { taps } = options ;
81+ const { length } = taps ;
82+ const fns = Array . from ( { length } ) ;
83+ for ( let i = 0 ; i < length ; i ++ ) {
84+ fns [ i ] = taps [ i ] . fn ;
85+ }
86+ instance . _x = fns ;
8187 }
8288
8389 /**
8490 * @param {{ type: "sync" | "promise" | "async", taps: Array<Tap>, interceptors: Array<Interceptor> } } options
8591 */
8692 init ( options ) {
8793 this . options = options ;
88- this . _args = [ ...options . args ] ;
94+ // slice() avoids the iterator protocol overhead of [...arr].
95+ // eslint-disable-next-line unicorn/prefer-spread
96+ this . _args = options . args . slice ( ) ;
97+ this . _joinedArgs = undefined ;
8998 }
9099
91100 deinit ( ) {
92101 this . options = undefined ;
93102 this . _args = undefined ;
103+ this . _joinedArgs = undefined ;
94104 }
95105
96106 contentWithInterceptors ( options ) {
@@ -165,7 +175,10 @@ class HookCodeFactory {
165175 }
166176
167177 needContext ( ) {
168- for ( const tap of this . options . taps ) if ( tap . context ) return true ;
178+ const { taps } = this . options ;
179+ for ( let i = 0 ; i < taps . length ; i ++ ) {
180+ if ( taps [ i ] . context ) return true ;
181+ }
169182 return false ;
170183 }
171184
@@ -274,17 +287,30 @@ class HookCodeFactory {
274287 doneReturns,
275288 rethrowIfPossible
276289 } ) {
277- if ( this . options . taps . length === 0 ) return onDone ( ) ;
278- const firstAsync = this . options . taps . findIndex ( ( t ) => t . type !== "sync" ) ;
290+ const { taps } = this . options ;
291+ const tapsLength = taps . length ;
292+ if ( tapsLength === 0 ) return onDone ( ) ;
293+ // Inlined findIndex to avoid the callback allocation.
294+ let firstAsync = - 1 ;
295+ for ( let i = 0 ; i < tapsLength ; i ++ ) {
296+ if ( taps [ i ] . type !== "sync" ) {
297+ firstAsync = i ;
298+ break ;
299+ }
300+ }
279301 const somethingReturns = resultReturns || doneReturns ;
302+ // doneBreak doesn't depend on the loop variable - hoist to allocate once.
303+ const doneBreak = ( skipDone ) => {
304+ if ( skipDone ) return "" ;
305+ return onDone ( ) ;
306+ } ;
280307 let code = "" ;
281308 let current = onDone ;
282309 let unrollCounter = 0 ;
283- for ( let j = this . options . taps . length - 1 ; j >= 0 ; j -- ) {
310+ for ( let j = tapsLength - 1 ; j >= 0 ; j -- ) {
284311 const i = j ;
285312 const unroll =
286- current !== onDone &&
287- ( this . options . taps [ i ] . type !== "sync" || unrollCounter ++ > 20 ) ;
313+ current !== onDone && ( taps [ i ] . type !== "sync" || unrollCounter ++ > 20 ) ;
288314 if ( unroll ) {
289315 unrollCounter = 0 ;
290316 code += `function _next${ i } () {\n` ;
@@ -293,10 +319,6 @@ class HookCodeFactory {
293319 current = ( ) => `${ somethingReturns ? "return " : "" } _next${ i } ();\n` ;
294320 }
295321 const done = current ;
296- const doneBreak = ( skipDone ) => {
297- if ( skipDone ) return "" ;
298- return onDone ( ) ;
299- } ;
300322 const content = this . callTap ( i , {
301323 onError : ( error ) => onError ( i , error , done , doneBreak ) ,
302324 onResult :
@@ -370,31 +392,35 @@ class HookCodeFactory {
370392 rethrowIfPossible,
371393 onTap = ( i , run ) => run ( )
372394 } ) {
373- if ( this . options . taps . length <= 1 ) {
395+ const { taps } = this . options ;
396+ const tapsLength = taps . length ;
397+ if ( tapsLength <= 1 ) {
374398 return this . callTapsSeries ( {
375399 onError,
376400 onResult,
377401 onDone,
378402 rethrowIfPossible
379403 } ) ;
380404 }
405+ // done and doneBreak don't depend on the loop variable - hoist them
406+ // so they're allocated once per compile instead of once per tap.
407+ const done = ( ) => {
408+ if ( onDone ) return "if(--_counter === 0) _done();\n" ;
409+ return "--_counter;" ;
410+ } ;
411+ const doneBreak = ( skipDone ) => {
412+ if ( skipDone || ! onDone ) return "_counter = 0;\n" ;
413+ return "_counter = 0;\n_done();\n" ;
414+ } ;
381415 let code = "" ;
382416 code += "do {\n" ;
383- code += `var _counter = ${ this . options . taps . length } ;\n` ;
417+ code += `var _counter = ${ tapsLength } ;\n` ;
384418 if ( onDone ) {
385419 code += "var _done = (function() {\n" ;
386420 code += onDone ( ) ;
387421 code += "});\n" ;
388422 }
389- for ( let i = 0 ; i < this . options . taps . length ; i ++ ) {
390- const done = ( ) => {
391- if ( onDone ) return "if(--_counter === 0) _done();\n" ;
392- return "--_counter;" ;
393- } ;
394- const doneBreak = ( skipDone ) => {
395- if ( skipDone || ! onDone ) return "_counter = 0;\n" ;
396- return "_counter = 0;\n_done();\n" ;
397- } ;
423+ for ( let i = 0 ; i < tapsLength ; i ++ ) {
398424 code += "if(_counter <= 0) break;\n" ;
399425 code += onTap (
400426 i ,
@@ -428,13 +454,22 @@ class HookCodeFactory {
428454 }
429455
430456 args ( { before, after } = { } ) {
457+ // Hot during code generation (called once per tap + per interceptor).
458+ // Cache the common no-before/no-after result so we only join once.
459+ if ( before === undefined && after === undefined ) {
460+ let joined = this . _joinedArgs ;
461+ if ( joined === undefined ) {
462+ joined = this . _args . length === 0 ? "" : this . _args . join ( ", " ) ;
463+ this . _joinedArgs = joined ;
464+ }
465+ return joined ;
466+ }
431467 let allArgs = this . _args ;
432468 if ( before ) allArgs = [ before , ...allArgs ] ;
433469 if ( after ) allArgs = [ ...allArgs , after ] ;
434470 if ( allArgs . length === 0 ) {
435471 return "" ;
436472 }
437-
438473 return allArgs . join ( ", " ) ;
439474 }
440475
0 commit comments