Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider Blob.fromStream (returns a Promise<Blob>) #140

Open
domenic opened this issue Aug 30, 2019 · 6 comments
Open

Consider Blob.fromStream (returns a Promise<Blob>) #140

domenic opened this issue Aug 30, 2019 · 6 comments

Comments

@domenic
Copy link
Contributor

domenic commented Aug 30, 2019

I'm filing this as a tracking issue, although I don't think it's particularly urgent. But maybe web developers have run into this more, in which case hearing from them would be great.

Anyway, right now, to convert a ReadableStream to a Blob, you have to do

const blob = await new Response(stream).blob();

which is pretty silly. Just as blob.stream() was added to avoid the silly new Request(blob).body pattern, perhaps we should consider adding a promise-returning Blob.fromStream() so you could do

const blob = await Blob.fromStream(stream);

There is a bigger savings if we add an options parameter:

const blob1 = new Blob([await new Response(stream).blob()], options);
const blob2 = Blob.fromStream(stream, options);

Points to consider:

  • We could instead make this stream.toBlob(), but I feel the layering of keeping streams ignorant of blobs is a bit cleaner.
  • We may not want to introduce this because it's better if people avoid using blobs? (Or is it just blob URLs that we dislike?)
  • If someone wanted a File, we could either add File.fromStream(stream, fileName, options) or we could have people do new File([Blob.fromStream(stream)], fileName, options). Probably File.fromStream() is nicer.
  • This does not allow creating a blob that represents an "in progress" stream, which I've heard people have wanted in order to have self-referential blobs. That would require new concepts and infrastructure. This proposal is basically just exposing existing infrastructure in a more straightforward way.
@annevk
Copy link
Member

annevk commented Sep 4, 2019

We only dislike blob: URLs (and have a solution, but nobody with the time to refactor all relevant entry points) as far as I know.

@mkruisselbrink
Copy link
Collaborator

Yep, blob URLs are bad, both blobs in general are fine. I'd be supportive of adding a Blob.fromStream method.

@guest271314
Copy link

AFAICT

const blob = await new Response(stream).blob();

throws a TypeError

Promise {<rejected>: TypeError: Failed to fetch}

The concept appears to be similar what is possible with Native File System, e.g.,

const fileFromStream = async(stream, fileName) =>  {
  const dir = await FileSystemDirectoryHandle.getSystemDirectory({ type: 'sandbox' });
  await stream
    .pipeTo(await (await dir.getFile(fileName, { create: true }))
    .createWritable(), { preventCancel: true });
  const file = (await dir.getFile(fileName)).getFile();
  // cleanup potential lingering temporary file (until page refreshed) in sandbox
  await dir.removeEntry(fileName);
  // sanity check
  for await(let entry of dir.getEntries()) {
    console.log(await entry);
    if (entry.isDirectory) await entry.removeRecursively();
    else await dir.removeEntry(fileName);
  }
  return file;
}

fileFromStream(new ReadableStream({start(c) {c.enqueue('1'); c.close()}}), 'file')
.then(console.log, console.error);

@guest271314
Copy link

@mkruisselbrink

Yep, blob URLs are bad

Can you explain why?

What is the available substitute?

@jimmywarting
Copy link

jimmywarting commented Feb 3, 2022

Yep, blob URLs are bad

Can you explain why?

I would also like to know why. I presume it's b/c developers forget to revoke them? but they are currently so "badly" needed b/c there is no substitute for creating video/audio urls, download links, web workers, iframes, css & font link's from dynamic generated content. if we want to get rid of them then you should be able to create them from a ReadableStream, Request or Response or a Blob directly.


In node-fetch / fetch-blob package I have been figuring about such Blob.fromStream method myself. Edit It now exist as const blob = await createTemporaryBlob(iterable, { type, signal }). and it's somewhat similar to #140 (comment)
I found that NodeJS new stream consumer (node:stream/consumers).blob(iterable) to be somewhat useful but one thing i did not like about it was that I had to do new Blob([part], { type: contentType }) afterwards, b/c it currently lacks options for the type.

One thing i found useful about this Blob.fromStream proposal is that we could then have a way of creating quite large blobs backed up by the file system if this is needed. (could be useful for eg FileSaver.js)


if we could hint about a known size then there wouldn't even be a reason for a person to have to wait for the concatination to finish either, you could return a blob directly.

const blob = Blob.fromStream(stream, { size: 1024 })

I think that the ability to generate your own blob backed with whatever method you may prefer would be a neat feature.

A pretty useful thing i built in my arbitrary fetch-blob was the ability to create a File/Blob-look-a-like item backed up by the filesystem that could then be passed to my own Blob constructor, it didn't need to read anything into memory. As long as it quack and walks like a duck then i could handle them.
(now undici have adapted theirs FormData impl to be able to accept any 3th party BlobLike items also - same as node-fetch's FormData implementation)

So instead of having blob = await blobFromPath('./readme.md') i could maybe have something like:

var blob = Blob.from({
  size: 1024,

  type: 'text/plain',
  
  // `start` and `end` would be normalized by eg: `blob.slice(-3)` 
  // to never be larger than blob size, or below 0
  slice (start, end) {
    // return a new blob with a different size/offset
    return Blob.from(...)
  },

  /**
   * return your own readable stream method, that could
   * technically be called multiple times over and over.
   * 
   * @return {ReadableStream<Uint8Array>}
   */
  stream() {
    // create your own readable stream method that returns the data
    const rs = fs.createReadStream(path, { start, end })
    const wrs = stream.Readable.toWeb(rs)
    // const [rs1, rs2] = wrs.tee() // using `tee()` could provide a way to cache the body if you need to reuse it.
    return wrs
  }
})

this is essentially what i have built in fetch-blob (but in another format) and then wrapped blob-like items around a new blob with new Blob([ blobLikeObject ])
I have also made a Blob/File like class out of a http request that supports byte ranges in order to parse zip files and download only the parts of the zip and unpack individual files from the entire thing. this was also slice-able

if they could somehow be representable by a native Blob class then you could do so much more with them. like:

  • Being able to creating a Blob URL \w URL.createObjectURL without having the content ready at the initial face
    • you could create downloadable links <a href="blob:url" download="filename.txt"> instantly instead of having to make a custom fetch Request that requires api-key headers, get it as a blob and then save it, only to risk getting blocked by the isTrusted no longer being a user interacted event b/c it took longer than 3s to download
    • and play it in a video element.
  • Create Web Worker urls
  • Save them to IndexedDB as native Blobs
  • Create Blob/Files backed up by cloud providers without even having to download anything.
    • you would essentially be creating a file "handle"

but all of this can't work cuz they are only blob look-a-like items and you can't create other native blobs with this made up blob parts. So this currently dose not work:

new globalThis.Blob([ 'prefix', blobLikeItem ], { type })`

which is a bit sad

@jimmywarting
Copy link

jimmywarting commented Dec 31, 2022

This does not allow creating a blob that represents an "in progress" stream, which I've heard people have wanted in order to have self-referential blobs. That would require new concepts and infrastructure. This proposal is basically just exposing existing infrastructure in a more straightforward way.

This is something i badly want to have right now. I feel like Blob.from({...}) ☝️ could solve this problem and so much more. Either that or the option to accept other blob-like objects. aka: new File([ blobLike ], fileName, options)

Even being able to create File that are not in progress at all would be useful too. (just reading the stream when it calls for it) would be useful as a way to create cloud provided file handles as a way to list the items in a directory without having to download everything

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants