jimmywarting / StreamSaver.js

StreamSaver writes stream to the filesystem directly asynchronous
https://jimmywarting.github.io/StreamSaver.js/example.html
MIT License
3.98k stars 413 forks source link

Download doesn't start #154

Closed chebum closed 4 years ago

chebum commented 4 years ago

I'm trying to figure out how to use the library without a ReadableStream. For a test I made a simple function that creates a test.txt file:

async function fn1() {
    const writeStream = streamSaver.createWriteStream("test.txt");
    const writer = writeStream.getWriter();
    await writer.write("test");
    await writer.close();
        document.write("download started")
}
fn1();

Here is the script on Codepen: https://codepen.io/chebum/pen/mdepePz

However, the download is immediately cancelled both in Chrome, and in Chromium-based Edge : image

Am I missing something? Should I call some other function to make the download happen?

jimmywarting commented 4 years ago

what you did wrong

you almost got it right. the lib was designed in a way to intercept a request in service worker with a readableStream

evt.respondWith(new Response(new ReadableStream(...)))

And the response constructor can only accept a byte readable stream. So the chunks has to be Uint8Arrays to work correctly. Now i haven't made any magical conversion from string, blobs, arraybuffer/views to solve this - so i can keep the lib smaller.

solution

So to fix your code just add the encoder:

async function fn1() {
    const writeStream = streamSaver.createWriteStream("test.txt");
    const writer = writeStream.getWriter();
        const encoder = new TextEncoder();
    await writer.write(encoder.encode("test"));
    await writer.close();
        document.write("download started");
}
fn1();

there is also other ways to solve it, but note that this would only work in Blink atm.

new Response('test').body.pipeTo(streamSaver.createWriteStream("test.txt"));

Reflection (lesson learned)

If i where to redo the hole lib then i wouldn't have called it streamSaver, I would probably not have used the WritableStream either. I would just have used whatever transferable object you would have thrown to the service worker and responded with that instead. my first design where actually to use fetch instead of postMessage - but it dose not work

self.onfetch = evt => {
  if (storeTemp) {
    evt.respondWith(storeTemp)
    delete storeTemp
  }
  storeTemp = new Response(evt.request.body)
  evt.respondWith(new Response(downloadUrl))
}

But that did not work and the purpose of this lib was to solve a RAM issue and have a writable stream that you could write to pice by pice. Then i could also leave out the stream polyfill that have bother me a tiny bit

If the request body where able to accept a readableStream (as it should - but non of the browser supports it yet) then it would just be a pipe that echo back the request

async function saveAs(object, name = 'unknown') {
  const res = await fetch('https://streamsaver.io/echo', {
    method: 'post',
    headers: {content-length, name},
    body: object
  })

  downloadIframe.src = await res.body.text()
}

saveAs(new ReadableStream(...), 'test.txt')
saveAs('test', 'test.txt')

// then if you really would like to have writeable stream you would do:
const { writable, readable } = new TransformStream()
saveAs(readable, 'test.txt')
doThingsWith(writable)

Alternative

You could start using the native-file-system today that i have built a "polyfill" for: https://github.com/jimmywarting/native-file-system-adapter

it works a bit different and you can save string, blobs, and other stuff with that also (not just streams) to learn more about how native-file-system works: look into: https://github.com/WICG/native-file-system

the-dev1 commented 4 years ago

To start my new project, should I adopt StreamSaver.js or Native file system adapter for downloading large data? What is the current download size limit on both?

jimmywarting commented 4 years ago

Native file system adapter use same service worker tech as streamsaver when saving. And it also doesn't depends on some mitm.

I would go with the native file system adapter for a new project. It's a new "web standard" and maybe one day later you no longer need to depend on a package

Native file system also have a file/folder picker, and a sandboxed filesystem that you can use

aggregate1166877 commented 1 year ago

@jimmywarting Thanks for the info. What do I Google search to get a "native file system adapter"? I tried searching exactly that but I'm getting a whole bunch of third-party libraries.

I'm assuming you mean we no longer need third-party libraries, or do I misunderstand? I'm assuming you mean there's a native function for this but I cannot find it.