Search code examples
javascriptuser-experiencewebassembly

Get WebAssembly instantiateStreaming progress


WebAssembly.instantiateStreaming is the fastest way to download and instantiate a .wasm module however for large .wasm files it can still take a long time. Simply displaying a spinner does not provide enough user feedback in this case.

Is there a way to use the WebAssembly.instantiateStreaming api and get some form of progress event so that an eta can displayed to the user? Ideally I would like to be able to display a percentage progress bar / estimated time left indicator so user's know how long they will have to wait.


Solution

  • Building off the answer here.

    To get the progress of WebAssembly.instantiateStreaming / WebAssembly.compileStreaming create a new Fetch Response with a custom ReadableStream which implements it's own controller.

    Example:

    // Get your normal fetch response
    var response = await fetch('https://www.example.com/example.wasm'); 
    
    // Note - If you are compressing your .wasm file the Content-Length will be incorrect
    // One workaround is to use a custom http header to manually specify the uncompressed size 
    var contentLength = response.headers.get('Content-Length');
    
    var total = parseInt(contentLength, 10);
    var loaded = 0;
    
    function progressHandler(bytesLoaded, totalBytes)
    {
        // Do what you want with this info...
    }
    
    var res = new Response(new ReadableStream({
            async start(controller) {
                var reader = response.body.getReader();
                for (;;) {
                    var {done, value} = await reader.read();
    
                    if (done)
                    {
                        progressHandler(total, total)
                        break
                    }
    
                    loaded += value.byteLength;
                    progressHandler(loaded, total)
                    controller.enqueue(value);
                }
                controller.close();
            },
        }, {
            "status" : response.status,
            "statusText" : response.statusText
        }));
    
    // Make sure to copy the headers!
    // Wasm is very picky with it's headers and it will fail to compile if they are not
    // specified correctly.
    for (var pair of response.headers.entries()) {
        res.headers.set(pair[0], pair[1]);
    }
    
    // The response (res) can now be passed to any of the streaming methods as normal
    var promise = WebAssembly.instantiateStreaming(res)