Skip to content

Commit 24ef7d6

Browse files
committed
lib: fix blob.stream() causing hanging promises
Refs: nodejs#47993 (comment)
1 parent d81151e commit 24ef7d6

File tree

2 files changed

+63
-28
lines changed

2 files changed

+63
-28
lines changed

lib/internal/blob.js

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -329,34 +329,38 @@ class Blob {
329329
pull(c) {
330330
const { promise, resolve, reject } = createDeferredPromise();
331331
this.pendingPulls.push({ resolve, reject });
332-
reader.pull((status, buffer) => {
333-
// If pendingPulls is empty here, the stream had to have
334-
// been canceled, and we don't really care about the result.
335-
// we can simply exit.
336-
if (this.pendingPulls.length === 0) {
337-
return;
338-
}
339-
const pending = this.pendingPulls.shift();
340-
if (status === 0) {
341-
// EOS
342-
c.close();
343-
pending.resolve();
344-
return;
345-
} else if (status < 0) {
346-
// The read could fail for many different reasons when reading
347-
// from a non-memory resident blob part (e.g. file-backed blob).
348-
// The error details the system error code.
349-
const error = lazyDOMException('The blob could not be read', 'NotReadableError');
350-
351-
c.error(error);
352-
pending.reject(error);
353-
return;
354-
}
355-
if (buffer !== undefined) {
356-
c.enqueue(new Uint8Array(buffer));
357-
}
358-
pending.resolve();
359-
});
332+
const readNext = () => {
333+
reader.pull((status, buffer) => {
334+
// If pendingPulls is empty here, the stream had to have
335+
// been canceled, and we don't really care about the result.
336+
// We can simply exit.
337+
if (this.pendingPulls.length === 0) {
338+
return;
339+
}
340+
if (status === 0) {
341+
// EOS
342+
c.close();
343+
const pending = this.pendingPulls.shift();
344+
pending.resolve();
345+
return;
346+
} else if (status < 0) {
347+
// The read could fail for many different reasons when reading
348+
// from a non-memory resident blob part (e.g. file-backed blob).
349+
// The error details the system error code.
350+
const error = lazyDOMException('The blob could not be read', 'NotReadableError');
351+
const pending = this.pendingPulls.shift();
352+
c.error(error);
353+
pending.reject(error);
354+
return;
355+
}
356+
if (buffer !== undefined) {
357+
c.enqueue(new Uint8Array(buffer));
358+
}
359+
// We keep reading until we either reach EOS or some error
360+
queueMicrotask(() => readNext());
361+
});
362+
};
363+
readNext();
360364
return promise;
361365
},
362366
cancel(reason) {

test/parallel/test-blob.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,37 @@ assert.throws(() => new Blob({}), {
237237
assert(res.done);
238238
})().then(common.mustCall());
239239

240+
(async () => {
241+
const b = new Blob(Array(10).fill('hello'));
242+
const reader = b.stream().getReader();
243+
const chunks = [];
244+
while (true) {
245+
const res = await reader.read();
246+
if (res.done) break;
247+
assert.strictEqual(res.value.byteLength, 5);
248+
chunks.push(res.value);
249+
}
250+
assert.strictEqual(chunks.length, 10);
251+
})().then(common.mustCall());
252+
253+
(async () => {
254+
const b = new Blob(Array(10).fill('hello'));
255+
const reader = b.stream().getReader();
256+
const chunks = [];
257+
while (true) {
258+
const res = await reader.read();
259+
if (chunks.length === 5) {
260+
reader.cancel('boom');
261+
break;
262+
}
263+
if (res.done) break;
264+
assert.strictEqual(res.value.byteLength, 5);
265+
chunks.push(res.value);
266+
}
267+
assert.strictEqual(chunks.length, 5);
268+
reader.closed.then(common.mustCall());
269+
})().then(common.mustCall());
270+
240271
{
241272
const b = new Blob(['hello\n'], { endings: 'native' });
242273
assert.strictEqual(b.size, EOL.length + 5);

0 commit comments

Comments
 (0)