* Resolve when the write stream emits `drain`, or reject on `error` / * `close` / signal abort — avoids the hang from awaiting a one-shot * `drain` event that never fires because the stream tore down first.
(file: NodeJS.WritableStream, signal?: AbortSignal)
| 103 | * `drain` event that never fires because the stream tore down first. |
| 104 | */ |
| 105 | function waitForDrain(file: NodeJS.WritableStream, signal?: AbortSignal): Promise<void> { |
| 106 | return new Promise<void>((resolve, reject) => { |
| 107 | const cleanup = () => { |
| 108 | file.off("drain", onDrain); |
| 109 | file.off("error", onError); |
| 110 | file.off("close", onClose); |
| 111 | signal?.removeEventListener("abort", onAbort); |
| 112 | }; |
| 113 | const onDrain = (): void => { |
| 114 | cleanup(); |
| 115 | resolve(); |
| 116 | }; |
| 117 | const onError = (err: Error): void => { |
| 118 | cleanup(); |
| 119 | reject(err); |
| 120 | }; |
| 121 | const onClose = (): void => { |
| 122 | cleanup(); |
| 123 | reject(new Error("write stream closed before drain")); |
| 124 | }; |
| 125 | const onAbort = (): void => { |
| 126 | cleanup(); |
| 127 | const reason = signal?.reason; |
| 128 | reject(reason instanceof Error ? reason : new Error("Download aborted")); |
| 129 | }; |
| 130 | file.once("drain", onDrain); |
| 131 | file.once("error", onError); |
| 132 | file.once("close", onClose); |
| 133 | signal?.addEventListener("abort", onAbort, { once: true }); |
| 134 | }); |
| 135 | } |
| 136 | |
| 137 | function closeFile(file: NodeJS.WritableStream): Promise<void> { |
| 138 | return new Promise<void>((resolve) => { |