Description
When putObject() is called with a Readable stream that gets destroyed afterwards, the return promise never resolves or rejects.
Reproduction
Node.js version: v22.22.2
minio-js version: 8.0.7
import { Client } from 'minio';
import { PassThrough } from 'node:stream';
const s = new PassThrough().on('error', () => {});
const client = new Minio.Client({
endPoint: 'localhost',
port: 9000,
useSSL: false,
accessKey: 'minioadmin',
secretKey: 'minioadmin',
});
const promise = client.putObject('my-bucket', 'my-file', s);
s.destroy(new Error('stream error'));
try {
await promise;
} catch (error) {
console.log('error caught:', error);
}
Output:
> node test.js
Warning: Detected unsettled top-level await at file:///..../test.js
await promise;
^
Troubleshooting
Here in uploadStream, it uses body.pipe(chunkier) which returns chunkier, so the error listener is set on chunkier twice. When body gets destroyed, chunkier is left open and the Promise doesn't resolve or reject.
new Promise((resolve, reject) => {
body.pipe(chunkier).on('error', reject)
chunkier.on('end', resolve).on('error', reject)
}),
Suggested Fix
Use stream.pipeline() instead which provides automatic error forwarding and cleanup when streams finish or encounter errors.
new Promise((resolve, reject) => {
stream.pipeline(body, chunkier)
chunkier.on('end', resolve).on('error', reject)
}),
Description
When
putObject()is called with a Readable stream that gets destroyed afterwards, the return promise never resolves or rejects.Reproduction
Node.js version: v22.22.2
minio-js version: 8.0.7
Output:
Troubleshooting
Here in
uploadStream, it usesbody.pipe(chunkier)which returnschunkier, so the error listener is set onchunkiertwice. Whenbodygets destroyed,chunkieris left open and thePromisedoesn't resolve or reject.Suggested Fix
Use
stream.pipeline()instead which provides automatic error forwarding and cleanup when streams finish or encounter errors.