55 MathMax,
66 MathMin,
77 ObjectDefineProperty,
8- ObjectSetPrototypeOf,
98 PromiseResolve,
9+ PromiseReject,
10+ PromisePrototypeFinally,
11+ ReflectConstruct,
1012 RegExpPrototypeTest,
1113 StringPrototypeToLowerCase,
1214 Symbol,
@@ -16,14 +18,14 @@ const {
1618} = primordials ;
1719
1820const {
19- createBlob,
21+ createBlob : _createBlob ,
2022 FixedSizeBlobCopyJob,
2123} = internalBinding ( 'buffer' ) ;
2224
2325const { TextDecoder } = require ( 'internal/encoding' ) ;
2426
2527const {
26- JSTransferable ,
28+ makeTransferable ,
2729 kClone,
2830 kDeserialize,
2931} = require ( 'internal/worker/js_transferable' ) ;
@@ -44,6 +46,7 @@ const {
4446 AbortError,
4547 codes : {
4648 ERR_INVALID_ARG_TYPE ,
49+ ERR_INVALID_THIS ,
4750 ERR_BUFFER_TOO_LARGE ,
4851 }
4952} = require ( 'internal/errors' ) ;
@@ -56,17 +59,27 @@ const {
5659const kHandle = Symbol ( 'kHandle' ) ;
5760const kType = Symbol ( 'kType' ) ;
5861const kLength = Symbol ( 'kLength' ) ;
62+ const kArrayBufferPromise = Symbol ( 'kArrayBufferPromise' ) ;
5963
6064const disallowedTypeCharacters = / [ ^ \u{0020} - \u{007E} ] / u;
6165
6266let Buffer ;
67+ let ReadableStream ;
6368
6469function lazyBuffer ( ) {
6570 if ( Buffer === undefined )
6671 Buffer = require ( 'buffer' ) . Buffer ;
6772 return Buffer ;
6873}
6974
75+ function lazyReadableStream ( options ) {
76+ if ( ReadableStream === undefined ) {
77+ ReadableStream =
78+ require ( 'internal/webstreams/readablestream' ) . ReadableStream ;
79+ }
80+ return new ReadableStream ( options ) ;
81+ }
82+
7083function isBlob ( object ) {
7184 return object ?. [ kHandle ] !== undefined ;
7285}
@@ -89,16 +102,7 @@ function getSource(source, encoding) {
89102 return [ source . byteLength , source ] ;
90103}
91104
92- class InternalBlob extends JSTransferable {
93- constructor ( handle , length , type = '' ) {
94- super ( ) ;
95- this [ kHandle ] = handle ;
96- this [ kType ] = type ;
97- this [ kLength ] = length ;
98- }
99- }
100-
101- class Blob extends JSTransferable {
105+ class Blob {
102106 constructor ( sources = [ ] , options = { } ) {
103107 emitExperimentalWarning ( 'buffer.Blob' ) ;
104108 if ( sources === null ||
@@ -120,13 +124,15 @@ class Blob extends JSTransferable {
120124 if ( ! isUint32 ( length ) )
121125 throw new ERR_BUFFER_TOO_LARGE ( 0xFFFFFFFF ) ;
122126
123- super ( ) ;
124- this [ kHandle ] = createBlob ( sources_ , length ) ;
127+ this [ kHandle ] = _createBlob ( sources_ , length ) ;
125128 this [ kLength ] = length ;
126129
127130 type = `${ type } ` ;
128131 this [ kType ] = RegExpPrototypeTest ( disallowedTypeCharacters , type ) ?
129132 '' : StringPrototypeToLowerCase ( type ) ;
133+
134+ // eslint-disable-next-line no-constructor-return
135+ return makeTransferable ( this ) ;
130136 }
131137
132138 [ kInspect ] ( depth , options ) {
@@ -150,7 +156,7 @@ class Blob extends JSTransferable {
150156 const length = this [ kLength ] ;
151157 return {
152158 data : { handle, type, length } ,
153- deserializeInfo : 'internal/blob:InternalBlob '
159+ deserializeInfo : 'internal/blob:ClonedBlob '
154160 } ;
155161 }
156162
@@ -160,11 +166,35 @@ class Blob extends JSTransferable {
160166 this [ kLength ] = length ;
161167 }
162168
163- get type ( ) { return this [ kType ] ; }
169+ /**
170+ * @readonly
171+ * @type {string }
172+ */
173+ get type ( ) {
174+ if ( ! isBlob ( this ) )
175+ throw new ERR_INVALID_THIS ( 'Blob' ) ;
176+ return this [ kType ] ;
177+ }
164178
165- get size ( ) { return this [ kLength ] ; }
179+ /**
180+ * @readonly
181+ * @type {number }
182+ */
183+ get size ( ) {
184+ if ( ! isBlob ( this ) )
185+ throw new ERR_INVALID_THIS ( 'Blob' ) ;
186+ return this [ kLength ] ;
187+ }
166188
189+ /**
190+ * @param {number } [start]
191+ * @param {number } [end]
192+ * @param {string } [contentType]
193+ * @returns {Blob }
194+ */
167195 slice ( start = 0 , end = this [ kLength ] , contentType = '' ) {
196+ if ( ! isBlob ( this ) )
197+ throw new ERR_INVALID_THIS ( 'Blob' ) ;
168198 if ( start < 0 ) {
169199 start = MathMax ( this [ kLength ] + start , 0 ) ;
170200 } else {
@@ -188,49 +218,106 @@ class Blob extends JSTransferable {
188218
189219 const span = MathMax ( end - start , 0 ) ;
190220
191- return new InternalBlob (
192- this [ kHandle ] . slice ( start , start + span ) , span , contentType ) ;
221+ return createBlob (
222+ this [ kHandle ] . slice ( start , start + span ) ,
223+ span ,
224+ contentType ) ;
193225 }
194226
195- async arrayBuffer ( ) {
227+ /**
228+ * @returns {Promise<ArrayBuffer> }
229+ */
230+ arrayBuffer ( ) {
231+ if ( ! isBlob ( this ) )
232+ return PromiseReject ( new ERR_INVALID_THIS ( 'Blob' ) ) ;
233+
234+ // If there's already a promise in flight for the content,
235+ // reuse it, but only once. After the cached promise resolves
236+ // it will be cleared, allowing it to be garbage collected
237+ // as soon as possible.
238+ if ( this [ kArrayBufferPromise ] )
239+ return this [ kArrayBufferPromise ] ;
240+
196241 const job = new FixedSizeBlobCopyJob ( this [ kHandle ] ) ;
197242
198243 const ret = job . run ( ) ;
244+
245+ // If the job returns a value immediately, the ArrayBuffer
246+ // was generated synchronously and should just be returned
247+ // directly.
199248 if ( ret !== undefined )
200249 return PromiseResolve ( ret ) ;
201250
202251 const {
203252 promise,
204253 resolve,
205- reject
254+ reject,
206255 } = createDeferredPromise ( ) ;
256+
207257 job . ondone = ( err , ab ) => {
208258 if ( err !== undefined )
209259 return reject ( new AbortError ( ) ) ;
210260 resolve ( ab ) ;
211261 } ;
262+ this [ kArrayBufferPromise ] =
263+ PromisePrototypeFinally (
264+ promise ,
265+ ( ) => this [ kArrayBufferPromise ] = undefined ) ;
212266
213- return promise ;
267+ return this [ kArrayBufferPromise ] ;
214268 }
215269
270+ /**
271+ *
272+ * @returns {Promise<string> }
273+ */
216274 async text ( ) {
275+ if ( ! isBlob ( this ) )
276+ throw new ERR_INVALID_THIS ( 'Blob' ) ;
277+
217278 const dec = new TextDecoder ( ) ;
218279 return dec . decode ( await this . arrayBuffer ( ) ) ;
219280 }
281+
282+ /**
283+ * @returns {ReadableStream }
284+ */
285+ stream ( ) {
286+ if ( ! isBlob ( this ) )
287+ throw new ERR_INVALID_THIS ( 'Blob' ) ;
288+
289+ const self = this ;
290+ return new lazyReadableStream ( {
291+ async start ( controller ) {
292+ const ab = await self . arrayBuffer ( ) ;
293+ controller . enqueue ( new Uint8Array ( ab ) ) ;
294+ controller . close ( ) ;
295+ }
296+ } ) ;
297+ }
298+ }
299+
300+ function ClonedBlob ( ) {
301+ return makeTransferable ( ReflectConstruct ( function ( ) { } , [ ] , Blob ) ) ;
302+ }
303+ ClonedBlob . prototype [ kDeserialize ] = ( ) => { } ;
304+
305+ function createBlob ( handle , length , type = '' ) {
306+ return makeTransferable ( ReflectConstruct ( function ( ) {
307+ this [ kHandle ] = handle ;
308+ this [ kType ] = type ;
309+ this [ kLength ] = length ;
310+ } , [ ] , Blob ) ) ;
220311}
221312
222313ObjectDefineProperty ( Blob . prototype , SymbolToStringTag , {
223314 configurable : true ,
224315 value : 'Blob' ,
225316} ) ;
226317
227- InternalBlob . prototype . constructor = Blob ;
228- ObjectSetPrototypeOf (
229- InternalBlob . prototype ,
230- Blob . prototype ) ;
231-
232318module . exports = {
233319 Blob,
234- InternalBlob,
320+ ClonedBlob,
321+ createBlob,
235322 isBlob,
236323} ;
0 commit comments