@@ -84,7 +84,12 @@ export class Try<
8484 result ?: TryResult < TReturn > ;
8585 promise ?: Promise < TryResult < TReturn > > ;
8686 isAsync ?: boolean ;
87- finallyRan : boolean ;
87+ // Callbacks that have already fired for this shared execution. When
88+ // .default() produces a clone, both parent and child share `exec`; each
89+ // instance's .finally() callback must still fire exactly once. Keying by
90+ // callback reference keeps the guard per-instance while the state lives
91+ // on the shared execution.
92+ finallyRan : Set < ( ) => void | Promise < void > > ;
8893 // Set of breadcrumbConfig objects whose breadcrumbs have already been
8994 // emitted for this shared execution. Shared across .default() clones so
9095 // a parent + child referencing the same config emit breadcrumbs only
@@ -151,7 +156,7 @@ export class Try<
151156 this . fn = fn ;
152157 this . args = args ;
153158 this . config = { tags : { } } ;
154- this . exec = { state : 'pending' , finallyRan : false , breadcrumbsEmitted : new Set ( ) } ;
159+ this . exec = { state : 'pending' , finallyRan : new Set ( ) , breadcrumbsEmitted : new Set ( ) } ;
155160 this . local = { breadcrumbsAdded : false } ;
156161 // Only `AsyncFunction`s are thenable: `installThenable()` defines an owned
157162 // `.then` data property so `await new Try(asyncFn)` works without
@@ -745,9 +750,21 @@ export class Try<
745750 */
746751 private execute ( ) : TryResult < TReturn > | Promise < TryResult < TReturn > > {
747752 if ( this . exec . state === 'executed' && this . exec . result ) {
748- return this . exec . isAsync
749- ? ( this . exec . promise as Promise < TryResult < TReturn > > )
750- : this . exec . result ;
753+ if ( this . exec . isAsync ) {
754+ // Chain this instance's finally onto the settled promise so clones
755+ // (from .default()) each run their own finallyCallback exactly once.
756+ return ( this . exec . promise as Promise < TryResult < TReturn > > ) . then (
757+ ( result ) => {
758+ const ran = this . runFinallyCallback ( ) ;
759+ return isPromiseLike ( ran )
760+ ? Promise . resolve ( ran ) . then ( ( ) => result )
761+ : result ;
762+ } ,
763+ ) ;
764+ }
765+ // Sync cached path: run this instance's finally if not already run.
766+ void this . runFinallyCallback ( ) ;
767+ return this . exec . result ;
751768 }
752769
753770 if ( this . exec . promise ) {
@@ -813,13 +830,14 @@ export class Try<
813830 }
814831
815832 private runFinallyCallback ( ) : void | Promise < void > {
816- if ( ! this . config . finallyCallback || this . exec . finallyRan ) {
833+ const cb = this . config . finallyCallback ;
834+ if ( ! cb || this . exec . finallyRan . has ( cb ) ) {
817835 return ;
818836 }
819- this . exec . finallyRan = true ;
837+ this . exec . finallyRan . add ( cb ) ;
820838
821839 try {
822- const result = this . config . finallyCallback ( ) ;
840+ const result = cb ( ) ;
823841 if ( isPromiseLike ( result ) ) {
824842 return Promise . resolve ( result ) . catch ( ( err : unknown ) => {
825843 if ( this . config . debug ) {
0 commit comments