@@ -53,18 +53,34 @@ export interface HttpResponseBody {
5353 } ;
5454}
5555
56+ interface CancellablePromise < T > {
57+ promise : Promise < T > ;
58+ cancel : ( ) => void ;
59+ }
60+
5661/**
5762 * Returns a Promise that will be rejected after the given duration.
5863 * The error will be of type FunctionsError.
5964 *
6065 * @param millis Number of milliseconds to wait before rejecting.
6166 */
62- function failAfter ( millis : number ) : Promise < never > {
63- return new Promise ( ( _ , reject ) => {
64- setTimeout ( ( ) => {
65- reject ( new FunctionsError ( 'deadline-exceeded' , 'deadline-exceeded' ) ) ;
66- } , millis ) ;
67- } ) ;
67+ function failAfter ( millis : number ) : CancellablePromise < never > {
68+ // Node timers and browser timers are fundamentally incompatible, but we
69+ // don't care about the value here
70+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
71+ let timer : any | null = null ;
72+ return {
73+ promise : new Promise ( ( _ , reject ) => {
74+ timer = setTimeout ( ( ) => {
75+ reject ( new FunctionsError ( 'deadline-exceeded' , 'deadline-exceeded' ) ) ;
76+ } , millis ) ;
77+ } ) ,
78+ cancel : ( ) => {
79+ if ( timer ) {
80+ clearTimeout ( timer ) ;
81+ }
82+ }
83+ } ;
6884}
6985
7086/**
@@ -247,12 +263,16 @@ async function call(
247263 // Default timeout to 70s, but let the options override it.
248264 const timeout = options . timeout || 70000 ;
249265
266+ const failAfterHandle = failAfter ( timeout ) ;
250267 const response = await Promise . race ( [
251268 postJSON ( url , body , headers , functionsInstance . fetchImpl ) ,
252- failAfter ( timeout ) ,
269+ failAfterHandle . promise ,
253270 functionsInstance . cancelAllRequests
254271 ] ) ;
255272
273+ // Always clear the failAfter timeout
274+ failAfterHandle . cancel ( ) ;
275+
256276 // If service was deleted, interrupted response throws an error.
257277 if ( ! response ) {
258278 throw new FunctionsError (
0 commit comments