1- import { startPerformanceMeasure } from '@guardian/libs' ;
1+ import { isUndefined , startPerformanceMeasure } from '@guardian/libs' ;
22import { getOphan } from '../client/ophan/ophan' ;
33import type { RenderingTarget } from '../types/renderingTarget' ;
44
@@ -57,8 +57,8 @@ const timeoutify = <T>(
5757) : CandidateConfigWithTimeout < T > => {
5858 let timer : number | undefined ;
5959
60- const canShow = ( ) : Promise < CanShowResult < T > > => {
61- return new Promise ( ( resolve ) => {
60+ const canShow = ( ) : Promise < CanShowResult < T > > =>
61+ new Promise ( ( resolve ) => {
6262 const perfName = `messagePicker-canShow-${ candidateConfig . candidate . id } ` ;
6363 const { endPerformanceMeasure } = startPerformanceMeasure (
6464 'tx' ,
@@ -72,53 +72,34 @@ const timeoutify = <T>(
7272 slotName ,
7373 renderingTarget ,
7474 ) ;
75- cancelTimeout ( ) ;
7675 resolve ( { show : false } ) ;
7776 } , candidateConfig . timeoutMillis ) ;
7877 }
7978
80- try {
81- candidateConfig . candidate
82- . canShow ( )
83- . then ( ( result ) => {
84- resolve ( result ) ;
85-
86- const canShowTimeTaken = endPerformanceMeasure ( ) ;
87-
88- if ( candidateConfig . reportTiming ) {
89- void getOphan ( renderingTarget ) . then ( ( ophan ) => {
90- ophan . record ( {
91- // @ts -expect-error -- the relevant team should remove this call as it is dropped by Ophan
92- // see https://github.com/guardian/dotcom-rendering/pull/11438 further context
93- component : perfName ,
94- value : canShowTimeTaken ,
95- } ) ;
79+ candidateConfig . candidate
80+ . canShow ( )
81+ . then ( ( result ) => {
82+ resolve ( result ) ;
83+
84+ const canShowTimeTaken = endPerformanceMeasure ( ) ;
85+
86+ if ( candidateConfig . reportTiming ) {
87+ void getOphan ( renderingTarget ) . then ( ( ophan ) => {
88+ ophan . record ( {
89+ // @ts -expect-error -- the relevant team should remove this call as it is dropped by Ophan
90+ // see https://github.com/guardian/dotcom-rendering/pull/11438 further context
91+ component : perfName ,
92+ value : canShowTimeTaken ,
9693 } ) ;
97- }
98- } )
99- . catch ( ( e ) => {
100- console . error (
101- `timeoutify candidate - error: ${ String ( e ) } ` ,
102- ) ;
103- resolve ( { show : false } ) ;
104- } )
105- . finally ( ( ) => {
106- cancelTimeout ( ) ;
107- } ) ;
108- } catch ( error ) {
109- console . error ( `timeoutify candidate - error: ${ String ( error ) } ` ) ;
110- cancelTimeout ( ) ;
111- resolve ( { show : false } ) ;
112- }
94+ } ) ;
95+ }
96+ } )
97+ . catch ( ( e ) =>
98+ console . error ( `timeoutify candidate - error: ${ String ( e ) } ` ) ,
99+ ) ;
113100 } ) ;
114- } ;
115101
116- const cancelTimeout = ( ) => {
117- if ( timer !== undefined ) {
118- clearTimeout ( timer ) ;
119- timer = undefined ;
120- }
121- } ;
102+ const cancelTimeout = ( ) => ! isUndefined ( timer ) && clearTimeout ( timer ) ;
122103
123104 return {
124105 ...candidateConfig ,
@@ -130,71 +111,67 @@ const timeoutify = <T>(
130111 } ;
131112} ;
132113
133- const clearAllTimeouts = (
134- candidateConfigs : CandidateConfigWithTimeout < any > [ ] ,
135- ) => {
136- for ( const config of candidateConfigs ) {
137- config . cancelTimeout ( ) ;
138- }
139- } ;
114+ const clearAllTimeouts = ( messages : CandidateConfigWithTimeout < any > [ ] ) =>
115+ messages . map ( ( m ) => m . cancelTimeout ( ) ) ;
140116
141117const defaultShow = ( ) => null ;
142118
143- /**
144- * Sequential message picker that respects priority order.
145- *
146- * This processes candidates one by one in the order they appear in the array,
147- * ensuring that higher priority messages are always checked first.
148- */
149- export async function pickMessage (
119+ interface PendingMessage < T > {
120+ candidateConfig : CandidateConfigWithTimeout < T > ;
121+ canShow : Promise < CanShowResult < T > > ;
122+ }
123+
124+ interface WinningMessage < T > {
125+ meta : T ;
126+ candidate : Candidate < T > ;
127+ }
128+
129+ export const pickMessage = (
150130 { candidates, name } : SlotConfig ,
151131 renderingTarget : RenderingTarget ,
152- ) : Promise < ( ) => MaybeFC > {
153- const candidateConfigsWithTimeout = candidates . map ( ( c ) =>
154- timeoutify ( c , name , renderingTarget ) ,
155- ) ;
156-
157- try {
158- const settled = await Promise . allSettled (
159- candidateConfigsWithTimeout . map ( ( config ) =>
160- config . candidate . canShow ( ) ,
161- ) ,
132+ ) : Promise < ( ) => MaybeFC > =>
133+ new Promise ( ( resolve ) => {
134+ const candidateConfigsWithTimeout = candidates . map ( ( c ) =>
135+ timeoutify ( c , name , renderingTarget ) ,
136+ ) ;
137+ const results : PendingMessage < any > [ ] = candidateConfigsWithTimeout . map (
138+ ( candidateConfig ) => ( {
139+ candidateConfig ,
140+ canShow : candidateConfig . candidate . canShow ( ) ,
141+ } ) ,
162142 ) ;
163143
164- for ( let i = 0 ; i < candidateConfigsWithTimeout . length ; i ++ ) {
165- const config = candidateConfigsWithTimeout [ i ] ;
166- const result = settled [ i ] ;
167-
168- config ?. cancelTimeout ( ) ;
144+ const winnerResult = results . reduce <
145+ Promise < WinningMessage < any > | null >
146+ > ( async ( winningMessageSoFar , { candidateConfig, canShow } ) => {
147+ if ( await winningMessageSoFar ) {
148+ return winningMessageSoFar ;
149+ }
169150
170- if (
171- result ?. status === 'rejected' &&
172- result . reason instanceof Error
173- ) {
174- window . guardian . modules . sentry . reportError (
175- result . reason ,
176- `pickMessage: error checking ${ config ?. candidate . id } ` ,
177- ) ;
178- continue ;
151+ const result = await canShow ;
152+ candidateConfig . cancelTimeout ( ) ;
153+ if ( result . show ) {
154+ return {
155+ candidate : candidateConfig . candidate ,
156+ meta : result . meta ,
157+ } ;
179158 }
180159
181- const canShowResult =
182- result ?. status === 'fulfilled' ? result . value : undefined ;
183- if ( canShowResult ?. show ) {
160+ return winningMessageSoFar ;
161+ } , Promise . resolve ( null ) ) ;
162+
163+ winnerResult
164+ . then ( ( winner ) => {
184165 clearAllTimeouts ( candidateConfigsWithTimeout ) ;
185- return ( ) => config ?. candidate . show ( canShowResult . meta ) ?? null ;
186- }
187- }
188-
189- return defaultShow ;
190- } catch ( fatal ) {
191- if ( fatal instanceof Error ) {
192- window . guardian . modules . sentry . reportError (
193- fatal ,
194- `pickMessage: fatal error` ,
166+
167+ if ( winner === null ) {
168+ resolve ( defaultShow ) ;
169+ } else {
170+ const { candidate , meta } = winner ;
171+ resolve ( ( ) => candidate . show ( meta ) ) ;
172+ }
173+ } )
174+ . catch ( ( e ) =>
175+ console . error ( `pickMessage winner - error: ${ String ( e ) } ` ) ,
195176 ) ;
196- }
197- clearAllTimeouts ( candidateConfigsWithTimeout ) ;
198- return defaultShow ;
199- }
200- }
177+ } ) ;
0 commit comments