@@ -37,6 +37,7 @@ import {
3737
3838const maxErrorResponseBodySize = 10 * 1024 * 1024 ;
3939const prefixUrlRenamedErrorMessage = 'The `prefixUrl` option has been renamed `prefix` in v2 and enhanced to allow slashes in input. See also the new `baseUrl` option for improved flexibility with standard URL resolution: https://github.com/sindresorhus/ky#baseurl' ;
40+ const timedOutResponseData = Symbol ( 'timedOutResponseData' ) ;
4041
4142const createTextDecoder = ( contentType : string ) : TextDecoder => {
4243 const match = / ; \s * c h a r s e t \s * = \s * (?: " ( [ ^ " ] + ) " | ( [ ^ ; , \s ] + ) ) / i. exec ( contentType ) ;
@@ -504,8 +505,15 @@ export class Ky {
504505 async #getResponseData( response : Response ) : Promise < unknown > {
505506 // Even with request timeouts disabled, bound error-body reads so retries and error propagation
506507 // cannot be stalled indefinitely by never-ending response streams.
507- const errorDataTimeout = this . #options. timeout === false ? 10_000 : this . #options. timeout ;
508- const text = await this . #readResponseText( response , errorDataTimeout ) ;
508+ const readTimeout = this . #getErrorDataTimeout( ) ;
509+ const text = await this . #readResponseText( response , readTimeout . timeout ) ;
510+ if ( text === timedOutResponseData ) {
511+ if ( readTimeout . totalTimeoutReachedOnTimeout ) {
512+ throw new TimeoutError ( this . request ) ;
513+ }
514+
515+ return undefined ;
516+ }
509517
510518 if ( ! text ) {
511519 return undefined ;
@@ -515,7 +523,38 @@ export class Ky {
515523 return text ;
516524 }
517525
518- return this . #parseJson( text , errorDataTimeout ) ;
526+ const parseTimeout = this . #getErrorDataTimeout( ) ;
527+ const data = await this . #parseJson( text , parseTimeout . timeout ) ;
528+ if ( data === timedOutResponseData ) {
529+ if ( parseTimeout . totalTimeoutReachedOnTimeout ) {
530+ throw new TimeoutError ( this . request ) ;
531+ }
532+
533+ return undefined ;
534+ }
535+
536+ return data ;
537+ }
538+
539+ #getErrorDataTimeout( ) : { timeout : number ; totalTimeoutReachedOnTimeout : boolean } {
540+ const errorDataTimeout = this . #options. timeout === false ? 10_000 : this . #options. timeout ;
541+ const remainingTotal = this . #getRemainingTotalTimeout( ) ;
542+
543+ if ( remainingTotal === undefined ) {
544+ return {
545+ timeout : errorDataTimeout ,
546+ totalTimeoutReachedOnTimeout : false ,
547+ } ;
548+ }
549+
550+ if ( remainingTotal <= 0 ) {
551+ throw new TimeoutError ( this . request ) ;
552+ }
553+
554+ return {
555+ timeout : Math . min ( errorDataTimeout , remainingTotal ) ,
556+ totalTimeoutReachedOnTimeout : remainingTotal <= errorDataTimeout ,
557+ } ;
519558 }
520559
521560 #isJsonContentType( contentType : string ) : boolean {
@@ -524,7 +563,7 @@ export class Ky {
524563 return / \/ (?: .* [ . + - ] ) ? j s o n $ / . test ( mimeType ) ;
525564 }
526565
527- async #readResponseText( response : Response , timeoutMs : number ) : Promise < string | undefined > {
566+ async #readResponseText( response : Response , timeoutMs : number ) : Promise < string | typeof timedOutResponseData | undefined > {
528567 const { body} = response ;
529568 if ( ! body ) {
530569 try {
@@ -571,17 +610,17 @@ export class Ky {
571610 return chunks . join ( '' ) ;
572611 } ) ( ) ;
573612
574- const timeoutPromise = new Promise < undefined > ( resolve => {
613+ const timeoutPromise = new Promise < typeof timedOutResponseData > ( resolve => {
575614 const timeoutId = setTimeout ( ( ) => {
576- resolve ( undefined ) ;
615+ resolve ( timedOutResponseData ) ;
577616 } , timeoutMs ) ;
578617 void readAll . finally ( ( ) => {
579618 clearTimeout ( timeoutId ) ;
580619 } ) ;
581620 } ) ;
582621
583622 const result = await Promise . race ( [ readAll , timeoutPromise ] ) ;
584- if ( result === undefined ) {
623+ if ( result === timedOutResponseData ) {
585624 void reader . cancel ( ) . catch ( ( ) => undefined ) ;
586625 }
587626
@@ -593,9 +632,9 @@ export class Ky {
593632 try {
594633 return await Promise . race ( [
595634 Promise . resolve ( ) . then ( ( ) => ( this . #options. parseJson ?? JSON . parse ) ( text ) ) ,
596- new Promise < undefined > ( resolve => {
635+ new Promise < typeof timedOutResponseData > ( resolve => {
597636 timeoutId = setTimeout ( ( ) => {
598- resolve ( undefined ) ;
637+ resolve ( timedOutResponseData ) ;
599638 } , timeoutMs ) ;
600639 } ) ,
601640 ] ) ;
0 commit comments