| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 0dc167a commit 091a579
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -507,15 +507,24 @@ added: v15.7.0 | |||
| 507 | 507 | Creates and returns a new `Blob` containing a subset of this `Blob` objects | |
| 508 | 508 | data. The original `Blob` is not altered. | |
| 509 | 509 | ||
| 510 | + ### `blob.stream()` | ||
| 511 | + <!-- YAML | ||
| 512 | + added: REPLACEME | ||
| 513 | + --> | ||
| 514 | + | ||
| 515 | + * Returns: {ReadableStream} | ||
| 516 | + | ||
| 517 | + Returns a new `ReadableStream` that allows the content of the `Blob` to be read. | ||
| 518 | + | ||
| 510 | 519 | ### `blob.text()` | |
| 511 | 520 | <!-- YAML | |
| 512 | 521 | added: v15.7.0 | |
| 513 | 522 | --> | |
| 514 | 523 | ||
| 515 | 524 | * Returns: {Promise} | |
| 516 | 525 | ||
| 517 | - Returns a promise that resolves the contents of the `Blob` decoded as a UTF-8 | ||
| 518 | - string. | ||
| 526 | + Returns a promise that fulfills with the contents of the `Blob` decoded as a | ||
| 527 | + UTF-8 string. | ||
| 519 | 528 | ||
| 520 | 529 | ### `blob.type` | |
| 521 | 530 | <!-- YAML | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -5,8 +5,10 @@ const { | |||
| 5 | 5 | MathMax, | |
| 6 | 6 | MathMin, | |
| 7 | 7 | ObjectDefineProperty, | |
| 8 | - ObjectSetPrototypeOf, | ||
| 9 | 8 | PromiseResolve, | |
| 9 | + PromiseReject, | ||
| 10 | + PromisePrototypeFinally, | ||
| 11 | + ReflectConstruct, | ||
| 10 | 12 | RegExpPrototypeTest, | |
| 11 | 13 | StringPrototypeToLowerCase, | |
| 12 | 14 | Symbol, | |
@@ -16,14 +18,14 @@ const { | |||
| 16 | 18 | } = primordials; | |
| 17 | 19 | ||
| 18 | 20 | const { | |
| 19 | - createBlob, | ||
| 21 | + createBlob: _createBlob, | ||
| 20 | 22 | FixedSizeBlobCopyJob, | |
| 21 | 23 | } = internalBinding('buffer'); | |
| 22 | 24 | ||
| 23 | 25 | const { TextDecoder } = require('internal/encoding'); | |
| 24 | 26 | ||
| 25 | 27 | const { | |
| 26 | - JSTransferable, | ||
| 28 | + makeTransferable, | ||
| 27 | 29 | kClone, | |
| 28 | 30 | kDeserialize, | |
| 29 | 31 | } = require('internal/worker/js_transferable'); | |
@@ -44,6 +46,7 @@ const { | |||
| 44 | 46 | AbortError, | |
| 45 | 47 | codes: { | |
| 46 | 48 | ERR_INVALID_ARG_TYPE, | |
| 49 | + ERR_INVALID_THIS, | ||
| 47 | 50 | ERR_BUFFER_TOO_LARGE, | |
| 48 | 51 | } | |
| 49 | 52 | } = require('internal/errors'); | |
@@ -56,17 +59,27 @@ const { | |||
| 56 | 59 | const kHandle = Symbol('kHandle'); | |
| 57 | 60 | const kType = Symbol('kType'); | |
| 58 | 61 | const kLength = Symbol('kLength'); | |
| 62 | + const kArrayBufferPromise = Symbol('kArrayBufferPromise'); | ||
| 59 | 63 | ||
| 60 | 64 | const disallowedTypeCharacters = /[^\u{0020}-\u{007E}]/u; | |
| 61 | 65 | ||
| 62 | 66 | let Buffer; | |
| 67 | + let ReadableStream; | ||
| 63 | 68 | ||
| 64 | 69 | function lazyBuffer() { | |
| 65 | 70 | if (Buffer === undefined) | |
| 66 | 71 | Buffer = require('buffer').Buffer; | |
| 67 | 72 | return Buffer; | |
| 68 | 73 | } | |
| 69 | 74 | ||
| 75 | + function lazyReadableStream(options) { | ||
| 76 | + if (ReadableStream === undefined) { | ||
| 77 | + ReadableStream = | ||
| 78 | + require('internal/webstreams/readablestream').ReadableStream; | ||
| 79 | + } | ||
| 80 | + return new ReadableStream(options); | ||
| 81 | + } | ||
| 82 | + | ||
| 70 | 83 | function isBlob(object) { | |
| 71 | 84 | return object?.[kHandle] !== undefined; | |
| 72 | 85 | } | |
@@ -89,16 +102,7 @@ function getSource(source, encoding) { | |||
| 89 | 102 | return [source.byteLength, source]; | |
| 90 | 103 | } | |
| 91 | 104 | ||
| 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 { | ||
| 102 | 106 | constructor(sources = [], options = {}) { | |
| 103 | 107 | emitExperimentalWarning('buffer.Blob'); | |
| 104 | 108 | if (sources === null || | |
@@ -120,13 +124,15 @@ class Blob extends JSTransferable { | |||
| 120 | 124 | if (!isUint32(length)) | |
| 121 | 125 | throw new ERR_BUFFER_TOO_LARGE(0xFFFFFFFF); | |
| 122 | 126 | ||
| 123 | - super(); | ||
| 124 | - this[kHandle] = createBlob(sources_, length); | ||
| 127 | + this[kHandle] = _createBlob(sources_, length); | ||
| 125 | 128 | this[kLength] = length; | |
| 126 | 129 | ||
| 127 | 130 | type = `${type}`; | |
| 128 | 131 | this[kType] = RegExpPrototypeTest(disallowedTypeCharacters, type) ? | |
| 129 | 132 | '' : StringPrototypeToLowerCase(type); | |
| 133 | + | ||
| 134 | + // eslint-disable-next-line no-constructor-return | ||
| 135 | + return makeTransferable(this); | ||
| 130 | 136 | } | |
| 131 | 137 | ||
| 132 | 138 | [kInspect](depth, options) { | |
@@ -150,7 +156,7 @@ class Blob extends JSTransferable { | |||
| 150 | 156 | const length = this[kLength]; | |
| 151 | 157 | return { | |
| 152 | 158 | data: { handle, type, length }, | |
| 153 | - deserializeInfo: 'internal/blob:InternalBlob' | ||
| 159 | + deserializeInfo: 'internal/blob:ClonedBlob' | ||
| 154 | 160 | }; | |
| 155 | 161 | } | |
| 156 | 162 | ||
@@ -160,11 +166,35 @@ class Blob extends JSTransferable { | |||
| 160 | 166 | this[kLength] = length; | |
| 161 | 167 | } | |
| 162 | 168 | ||
| 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 | + } | ||
| 164 | 178 | ||
| 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 | + } | ||
| 166 | 188 | ||
| 189 | + /** | ||
| 190 | + * @param {number} [start] | ||
| 191 | + * @param {number} [end] | ||
| 192 | + * @param {string} [contentType] | ||
| 193 | + * @returns {Blob} | ||
| 194 | + */ | ||
| 167 | 195 | slice(start = 0, end = this[kLength], contentType = '') { | |
| 196 | + if (!isBlob(this)) | ||
| 197 | + throw new ERR_INVALID_THIS('Blob'); | ||
| 168 | 198 | if (start < 0) { | |
| 169 | 199 | start = MathMax(this[kLength] + start, 0); | |
| 170 | 200 | } else { | |
@@ -188,49 +218,106 @@ class Blob extends JSTransferable { | |||
| 188 | 218 | ||
| 189 | 219 | const span = MathMax(end - start, 0); | |
| 190 | 220 | ||
| 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); | ||
| 193 | 225 | } | |
| 194 | 226 | ||
| 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 | + | ||
| 196 | 241 | const job = new FixedSizeBlobCopyJob(this[kHandle]); | |
| 197 | 242 | ||
| 198 | 243 | 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. | ||
| 199 | 248 | if (ret !== undefined) | |
| 200 | 249 | return PromiseResolve(ret); | |
| 201 | 250 | ||
| 202 | 251 | const { | |
| 203 | 252 | promise, | |
| 204 | 253 | resolve, | |
| 205 | - reject | ||
| 254 | + reject, | ||
| 206 | 255 | } = createDeferredPromise(); | |
| 256 | + | ||
| 207 | 257 | job.ondone = (err, ab) => { | |
| 208 | 258 | if (err !== undefined) | |
| 209 | 259 | return reject(new AbortError()); | |
| 210 | 260 | resolve(ab); | |
| 211 | 261 | }; | |
| 262 | + this[kArrayBufferPromise] = | ||
| 263 | + PromisePrototypeFinally( | ||
| 264 | + promise, | ||
| 265 | + () => this[kArrayBufferPromise] = undefined); | ||
| 212 | 266 | ||
| 213 | - return promise; | ||
| 267 | + return this[kArrayBufferPromise]; | ||
| 214 | 268 | } | |
| 215 | 269 | ||
| 270 | + /** | ||
| 271 | + * | ||
| 272 | + * @returns {Promise<string>} | ||
| 273 | + */ | ||
| 216 | 274 | async text() { | |
| 275 | + if (!isBlob(this)) | ||
| 276 | + throw new ERR_INVALID_THIS('Blob'); | ||
| 277 | + | ||
| 217 | 278 | const dec = new TextDecoder(); | |
| 218 | 279 | return dec.decode(await this.arrayBuffer()); | |
| 219 | 280 | } | |
| 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)); | ||
| 220 | 311 | } | |
| 221 | 312 | ||
| 222 | 313 | ObjectDefineProperty(Blob.prototype, SymbolToStringTag, { | |
| 223 | 314 | configurable: true, | |
| 224 | 315 | value: 'Blob', | |
| 225 | 316 | }); | |
| 226 | 317 | ||
| 227 | - InternalBlob.prototype.constructor = Blob; | ||
| 228 | - ObjectSetPrototypeOf( | ||
| 229 | - InternalBlob.prototype, | ||
| 230 | - Blob.prototype); | ||
| 231 | - | ||
| 232 | 318 | module.exports = { | |
| 233 | 319 | Blob, | |
| 234 | - InternalBlob, | ||
| 320 | + ClonedBlob, | ||
| 321 | + createBlob, | ||
| 235 | 322 | isBlob, | |
| 236 | 323 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -198,3 +198,21 @@ assert.throws(() => new Blob({}), { | |||
| 198 | 198 | 'Blob { size: 0, type: \'\' }'); | |
| 199 | 199 | assert.strictEqual(inspect(b, { depth: -1 }), '[Blob]'); | |
| 200 | 200 | } | |
| 201 | + | ||
| 202 | + { | ||
| 203 | + // The Blob has to be over a specific size for the data to | ||
| 204 | + // be copied asynchronously.. | ||
| 205 | + const b = new Blob(['hello', 'there'.repeat(820)]); | ||
| 206 | + assert.strictEqual(b.arrayBuffer(), b.arrayBuffer()); | ||
| 207 | + b.arrayBuffer().then(common.mustCall()); | ||
| 208 | + } | ||
| 209 | + | ||
| 210 | + (async () => { | ||
| 211 | + const b = new Blob(['hello']); | ||
| 212 | + const reader = b.stream().getReader(); | ||
| 213 | + let res = await reader.read(); | ||
| 214 | + assert.strictEqual(res.value.byteLength, 5); | ||
| 215 | + assert(!res.done); | ||
| 216 | + res = await reader.read(); | ||
| 217 | + assert(res.done); | ||
| 218 | + })().then(common.mustCall()); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments