mirror of
https://github.com/nodejs/node.git
synced 2025-08-15 13:48:44 +02:00
buffer: add Blob.prototype.stream method and other cleanups
Adds the `stream()` method to get a `ReadableStream` for the `Blob`. Also makes some other improvements to get the implementation closer to the API standard definition. Signed-off-by: James M Snell <jasnell@gmail.com> PR-URL: https://github.com/nodejs/node/pull/39693 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Bradley Farias <bradley.meck@gmail.com> Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
This commit is contained in:
parent
87d6fd7e69
commit
0bb2605f85
3 changed files with 145 additions and 31 deletions
|
@ -5,8 +5,10 @@ const {
|
|||
MathMax,
|
||||
MathMin,
|
||||
ObjectDefineProperty,
|
||||
ObjectSetPrototypeOf,
|
||||
PromiseResolve,
|
||||
PromiseReject,
|
||||
PromisePrototypeFinally,
|
||||
ReflectConstruct,
|
||||
RegExpPrototypeTest,
|
||||
StringPrototypeToLowerCase,
|
||||
Symbol,
|
||||
|
@ -16,14 +18,14 @@ const {
|
|||
} = primordials;
|
||||
|
||||
const {
|
||||
createBlob,
|
||||
createBlob: _createBlob,
|
||||
FixedSizeBlobCopyJob,
|
||||
} = internalBinding('buffer');
|
||||
|
||||
const { TextDecoder } = require('internal/encoding');
|
||||
|
||||
const {
|
||||
JSTransferable,
|
||||
makeTransferable,
|
||||
kClone,
|
||||
kDeserialize,
|
||||
} = require('internal/worker/js_transferable');
|
||||
|
@ -44,6 +46,7 @@ const {
|
|||
AbortError,
|
||||
codes: {
|
||||
ERR_INVALID_ARG_TYPE,
|
||||
ERR_INVALID_THIS,
|
||||
ERR_BUFFER_TOO_LARGE,
|
||||
}
|
||||
} = require('internal/errors');
|
||||
|
@ -56,10 +59,12 @@ const {
|
|||
const kHandle = Symbol('kHandle');
|
||||
const kType = Symbol('kType');
|
||||
const kLength = Symbol('kLength');
|
||||
const kArrayBufferPromise = Symbol('kArrayBufferPromise');
|
||||
|
||||
const disallowedTypeCharacters = /[^\u{0020}-\u{007E}]/u;
|
||||
|
||||
let Buffer;
|
||||
let ReadableStream;
|
||||
|
||||
function lazyBuffer() {
|
||||
if (Buffer === undefined)
|
||||
|
@ -67,6 +72,14 @@ function lazyBuffer() {
|
|||
return Buffer;
|
||||
}
|
||||
|
||||
function lazyReadableStream(options) {
|
||||
if (ReadableStream === undefined) {
|
||||
ReadableStream =
|
||||
require('internal/webstreams/readablestream').ReadableStream;
|
||||
}
|
||||
return new ReadableStream(options);
|
||||
}
|
||||
|
||||
function isBlob(object) {
|
||||
return object?.[kHandle] !== undefined;
|
||||
}
|
||||
|
@ -89,16 +102,7 @@ function getSource(source, encoding) {
|
|||
return [source.byteLength, source];
|
||||
}
|
||||
|
||||
class InternalBlob extends JSTransferable {
|
||||
constructor(handle, length, type = '') {
|
||||
super();
|
||||
this[kHandle] = handle;
|
||||
this[kType] = type;
|
||||
this[kLength] = length;
|
||||
}
|
||||
}
|
||||
|
||||
class Blob extends JSTransferable {
|
||||
class Blob {
|
||||
constructor(sources = [], options = {}) {
|
||||
emitExperimentalWarning('buffer.Blob');
|
||||
if (sources === null ||
|
||||
|
@ -120,13 +124,15 @@ class Blob extends JSTransferable {
|
|||
if (!isUint32(length))
|
||||
throw new ERR_BUFFER_TOO_LARGE(0xFFFFFFFF);
|
||||
|
||||
super();
|
||||
this[kHandle] = createBlob(sources_, length);
|
||||
this[kHandle] = _createBlob(sources_, length);
|
||||
this[kLength] = length;
|
||||
|
||||
type = `${type}`;
|
||||
this[kType] = RegExpPrototypeTest(disallowedTypeCharacters, type) ?
|
||||
'' : StringPrototypeToLowerCase(type);
|
||||
|
||||
// eslint-disable-next-line no-constructor-return
|
||||
return makeTransferable(this);
|
||||
}
|
||||
|
||||
[kInspect](depth, options) {
|
||||
|
@ -150,7 +156,7 @@ class Blob extends JSTransferable {
|
|||
const length = this[kLength];
|
||||
return {
|
||||
data: { handle, type, length },
|
||||
deserializeInfo: 'internal/blob:InternalBlob'
|
||||
deserializeInfo: 'internal/blob:ClonedBlob'
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -160,11 +166,35 @@ class Blob extends JSTransferable {
|
|||
this[kLength] = length;
|
||||
}
|
||||
|
||||
get type() { return this[kType]; }
|
||||
/**
|
||||
* @readonly
|
||||
* @type {string}
|
||||
*/
|
||||
get type() {
|
||||
if (!isBlob(this))
|
||||
throw new ERR_INVALID_THIS('Blob');
|
||||
return this[kType];
|
||||
}
|
||||
|
||||
get size() { return this[kLength]; }
|
||||
/**
|
||||
* @readonly
|
||||
* @type {number}
|
||||
*/
|
||||
get size() {
|
||||
if (!isBlob(this))
|
||||
throw new ERR_INVALID_THIS('Blob');
|
||||
return this[kLength];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} [start]
|
||||
* @param {number} [end]
|
||||
* @param {string} [contentType]
|
||||
* @returns {Blob}
|
||||
*/
|
||||
slice(start = 0, end = this[kLength], contentType = '') {
|
||||
if (!isBlob(this))
|
||||
throw new ERR_INVALID_THIS('Blob');
|
||||
if (start < 0) {
|
||||
start = MathMax(this[kLength] + start, 0);
|
||||
} else {
|
||||
|
@ -188,35 +218,96 @@ class Blob extends JSTransferable {
|
|||
|
||||
const span = MathMax(end - start, 0);
|
||||
|
||||
return new InternalBlob(
|
||||
this[kHandle].slice(start, start + span), span, contentType);
|
||||
return createBlob(
|
||||
this[kHandle].slice(start, start + span),
|
||||
span,
|
||||
contentType);
|
||||
}
|
||||
|
||||
async arrayBuffer() {
|
||||
/**
|
||||
* @returns {Promise<ArrayBuffer>}
|
||||
*/
|
||||
arrayBuffer() {
|
||||
if (!isBlob(this))
|
||||
return PromiseReject(new ERR_INVALID_THIS('Blob'));
|
||||
|
||||
// If there's already a promise in flight for the content,
|
||||
// reuse it, but only once. After the cached promise resolves
|
||||
// it will be cleared, allowing it to be garbage collected
|
||||
// as soon as possible.
|
||||
if (this[kArrayBufferPromise])
|
||||
return this[kArrayBufferPromise];
|
||||
|
||||
const job = new FixedSizeBlobCopyJob(this[kHandle]);
|
||||
|
||||
const ret = job.run();
|
||||
|
||||
// If the job returns a value immediately, the ArrayBuffer
|
||||
// was generated synchronously and should just be returned
|
||||
// directly.
|
||||
if (ret !== undefined)
|
||||
return PromiseResolve(ret);
|
||||
|
||||
const {
|
||||
promise,
|
||||
resolve,
|
||||
reject
|
||||
reject,
|
||||
} = createDeferredPromise();
|
||||
|
||||
job.ondone = (err, ab) => {
|
||||
if (err !== undefined)
|
||||
return reject(new AbortError());
|
||||
resolve(ab);
|
||||
};
|
||||
this[kArrayBufferPromise] =
|
||||
PromisePrototypeFinally(
|
||||
promise,
|
||||
() => this[kArrayBufferPromise] = undefined);
|
||||
|
||||
return promise;
|
||||
return this[kArrayBufferPromise];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async text() {
|
||||
if (!isBlob(this))
|
||||
throw new ERR_INVALID_THIS('Blob');
|
||||
|
||||
const dec = new TextDecoder();
|
||||
return dec.decode(await this.arrayBuffer());
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {ReadableStream}
|
||||
*/
|
||||
stream() {
|
||||
if (!isBlob(this))
|
||||
throw new ERR_INVALID_THIS('Blob');
|
||||
|
||||
const self = this;
|
||||
return new lazyReadableStream({
|
||||
async start(controller) {
|
||||
const ab = await self.arrayBuffer();
|
||||
controller.enqueue(new Uint8Array(ab));
|
||||
controller.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function ClonedBlob() {
|
||||
return makeTransferable(ReflectConstruct(function() {}, [], Blob));
|
||||
}
|
||||
ClonedBlob.prototype[kDeserialize] = () => {};
|
||||
|
||||
function createBlob(handle, length, type = '') {
|
||||
return makeTransferable(ReflectConstruct(function() {
|
||||
this[kHandle] = handle;
|
||||
this[kType] = type;
|
||||
this[kLength] = length;
|
||||
}, [], Blob));
|
||||
}
|
||||
|
||||
ObjectDefineProperty(Blob.prototype, SymbolToStringTag, {
|
||||
|
@ -224,13 +315,9 @@ ObjectDefineProperty(Blob.prototype, SymbolToStringTag, {
|
|||
value: 'Blob',
|
||||
});
|
||||
|
||||
InternalBlob.prototype.constructor = Blob;
|
||||
ObjectSetPrototypeOf(
|
||||
InternalBlob.prototype,
|
||||
Blob.prototype);
|
||||
|
||||
module.exports = {
|
||||
Blob,
|
||||
InternalBlob,
|
||||
ClonedBlob,
|
||||
createBlob,
|
||||
isBlob,
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue