-2

I really want to do simple thing - load some glsl fragment shaders from disk/server, and than initialize WebGL web-page. I'm really not web programmer, normally I prefer programing shaders and opengl in C++. I would never put my hands on javascript if there would not be WebGL. I did not realized how hard it could be just to load a file in javascript. Then somebody on stack overflow recommend to use fetch API which seems quite convenient. So I tried like this:

// somewhere in HTML body far far away
<script>
var str_RayTracer  = "//RayTracer";
var str_Primitives = "//Primitives";
fetch('RayTracer.glslf') .then(response => response.text()).then(text => str_RayTracer  = text );
fetch('Primitives.glslf').then(response => response.text()).then(text => str_Primitives = text );
init_GLSLScreen(str_Primitives, str_RayTracer); 
</script>

The problem is that fetch is some asynchronous bullshit, therefore init_GLSLScreen(str_Primitives, str_RayTracer) execute before the shaders are loaded => It throws an exception like cannot compile shaders and stuff .... I really don't want to stick into that asynchronous business, and I want to get out - back to decent synchonous programming - as fast as possible. because 1) debugging asynchronous stuff is hell, and 2) because the program have really nothing to do until the shaders are loaded.

It seems there is no simple way how to synchronize asynchronous and synchronous calls in javascript (which is strange - in every parallel programming language I know you have some synchronization points). There is nothing like wait_to_resolve there is just await, which (despite to what the name suggests) does not really wait for anything. It just push the problem further by forcing you to make the wrapper function async as well.

I can make it work this way:

<script>
var str_RayTracer  = "//RayTracer";
var str_Primitives = "//Primitives";
function initThis(txt){
   // for some reason I must take `txt` although I don't use it
   init_GLSLScreen(str_Primitives, str_RayTracer);
} 

async function loadPage(){
    str_Primitives = await fetch('Primitives.glslf').then( r => r.text() );
    str_RayTracer  = await fetch('RayTracer.glslf' ).then( r => r.text() );
    return 0; // for some reason I must return something
}

loadPage().then( txt => initThis(txt) );
//loadPage().then( initThis() ); // doesn't seem to work
</script>

But I don't like it, since initialization is inherently sequential operation. I don't see why I cannot write normal sequntial code, and instead should chain callback dependencies like this. That is like writing sequential program backwards.

I tried to read all threads about this issue on stack overflow ( e.g. 1 2 ), and there is no really useful answer. There is just "Oh, we don't do that here" meme. Best expressed here: How do I return the response from an asynchronous call?

Everywhere is written something like There is no way how to do it without blocking GUI. OK, so what? Tell me how to do it with blocking the GUI, please! Since the GUI cannot be even initialized until I download those files.

I understand that sometimes the non-blocking and asynchronous operations are useful, especially when you want to make responsive web-page. But sometimes not, sometimes you really want to wait until job is done, and the choice should be on programmer.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Prokop Hapala
  • 2,424
  • 2
  • 30
  • 59
  • 1
    A note about your comments: You don't need `txt` and you don't need to `return` anything from that function. Your last comment doesn't work because you're calling `loadThis()` and passing the result into `then`, instead of passing `loadThis` into then. This would work: `loadPage().then( initThis )` – Paul Oct 27 '19 at 15:31

1 Answers1

0

Tell me how to do it with blocking the GUI, please!

Use XMLHttpRequest which supports a synchronous mode, instead of fetch.

But really you shouldn't do that, it's essentially deprecated. Learn how to embrace asynchronous programming for the web :-) You were quite close actually. The asynchronous but completely sequential code would look like this - avoiding global variables for the promise results:

<script>
async function loadTextfile(path) {
    var response = await fetch(path);
    return response.text();
}
async function loadPage() {
    var str_Primitives = await loadTextfile('Primitives.glslf');
    var str_RayTracer  = await loadTextfile('RayTracer.glslf');

    init_GLSLScreen(str_Primitives, str_RayTracer);
}

loadPage().catch(err => { console.error(err); });
</script>

Now you can very easily improve performance by loading the two files concurrently - just change to

async function loadPage() {
    var [str_Primitives, str_RayTracer] = await Promise.all([
        loadTextfile('Primitives.glslf'),
        loadTextfile('RayTracer.glslf'),
    ]);
    init_GLSLScreen(str_Primitives, str_RayTracer);
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • so basically you say I should put the whole program inside an `async` function? OK, perhaps I can do that. Just what is the point of writing `async` when everything becomes `async`? – Prokop Hapala Oct 27 '19 at 19:00
  • Yes, doing that is the simplest solution when your whole program is sequential. The advantage is that a) it works with modern asynchronous APIs (like `fetch`) b) by making it asynchronous the browser stays responsive, allowing rendering or responding to events during `await`ing phases. (Granted, you might not need this when the *whole* page is really about the WebGL content, but it allows for stuff like a "Loading" text or animation, and allows embedding the page into something larger) – Bergi Oct 27 '19 at 19:22