2

I am trying to load glsl scripts as strings using the ES6 Promise and Fetch APIs. I thought I had a pretty elegant solution for getting the vertex and fragment shaders and creating a new programInfo with twgl.js

Promise.all([fetch(vert_url),fetch(frag_url))])
       .then((responses) => responses.map((response) => response.text()))
       .then((sources) => this.programInfo = twgl.createProgramInfo(gl, sources));

The problem is that it seems that response.text() is returning a Promise rather than a raw string. Inside twgl.createProgramInfo() it runs sources through a map, and then attempts to run indexOf on the results.

function createProgramInfo(gl, shaderSources, ...) {
    ...
    shaderSources = shaderSources.map(function (source) {
      // Lets assume if there is no \n it's an id
      if (source.indexOf("\n") < 0) {
      ...

Chrome throws a javascript error on the final line there:

Uncaught (in promise) TypeError: source.indexOf is not a function

I can't seem to figure out how to turn sources into real strings. Anybody have an idea how to get this working?

Note: This is actually in a React app created using create-react-app, that means webpack and babel are being used to transpile this from jsx.

  • @4castle Maybe consider turning your comment into an answer so this thread can be closed – gyre Mar 11 '17 at 22:34
  • @4castle 's answer is correct but from an implementation perspective, why not just combine the `then`s since the first one is not doing any async behavior? – Damon Mar 11 '17 at 22:48
  • @Damon If they don't use two `then` calls, they will end up with nested callbacks, which is unnecessary with promises. – 4castle Mar 11 '17 at 23:20
  • @4castle how so? you can just pass the map into `createProgramInfo` in the first `then`, something like `this.programInfo = twgl.createProgramInfo(gl, responses.map(_=>_.text()))` since there is no point in promisifying a synchronous map. – Damon Mar 12 '17 at 03:02
  • 1
    @Damon According to the OP, `.text()` returns a promise, not the actual string which their function expects, so there has to be another `.then` which waits for those promises. – 4castle Mar 12 '17 at 14:42
  • @4castle cool - misread that part +1 – Damon Mar 12 '17 at 18:44

1 Answers1

3

In order to convert an array of promises into a promise of an array, use Promise.all:

Promise.all([fetch(vert_url), fetch(frag_url)])
       .then(responses => Promise.all(responses.map(response => response.text())))
       .then(sources => this.programInfo = twgl.createProgramInfo(gl, sources));

Promise#then will evaluate the promise you return in the callback from Promise.all, and will make the next call to .then evaluate to the array of sources, which is what your code expects.


With a promise library like Bluebird, you can use Promise.map in order to make this more readable.

Promise.all([fetch(vert_url), fetch(frag_url)])
       .map(response => response.text())
       .then(sources => this.programInfo = twgl.createProgramInfo(gl, sources));
4castle
  • 32,613
  • 11
  • 69
  • 106
  • Nice, never heard of this technique, but one could also use `arguments` and not require an extra promise? Seems a little bit like abusing promises. – Ruan Mendes Mar 11 '17 at 23:19
  • @JuanMendes Perhaps you should post your own answer? This code is pretty straightforward (it's not some special technique), but if you know of another way, please share. – 4castle Mar 11 '17 at 23:24