Error Reporting

Is there any way (throwing a special exception?) to report a runtime error, so it shows up red with a marker like an Observable syntax does?

Doesn’t throw Error("my error") work for you?

Unfortunately no - probably due to where its getting thrown?:

function layout(layout) {
  function * _(parts, ...args) {
    const div = html`<div>...loading...</div>`;
    yield div;

    const str = parts.map((s, i) => s + (i < args.length ? args[i] : "")).join("");

    graphviz.layout(str, "svg", layout).then(html=>{
      div.innerHTML = html;
    }).catch(e=>{
      div.innerText = e.message;
      throw e;
    });
  }
  return _;
}

Does it work if you change

throw e;

to

throw Error(e.message);

?

I suspect that Observable relies on the fileName and lineNumber properties Observable relies on the stack property of an Error object to mark the error source. E.g., tracking won’t work either if you simply throw a string.

Afraid not, tried all the obvious ones:

  • throw e
  • throw Error(“Hello”);
  • throw new Error(“Hello”);

You need to return the error object. So in your generator you’ll have to yield the graphviz.layout promise. Check out this example:

function* errorView() {
  let resolve, reject;
  const p = new Promise((res, rej) => {
          resolve = res, reject = rej;
        }),
        timer = Promises.delay(5000).then(() => {
          throw Error('time\'s up!');
        }),
        yep = Object.assign(html`<button>Resolve`, {
          onclick() { resolve('Done!') }
        }),
        nope = Object.assign(html`<button>Reject`, {
         onclick() { reject(Error('Not gonna happen')) }
        });
  
  yield html`<div>Stand by, we're "loading" (5sec max). ${yep} ${nope}`;
  yield Promise.race([p, timer]).then(data => html`<div>${data}`)
}
errorView()

The main difference between your code and what I had, was that you end up returning two DOM Elements (one for the “loading” and one for the “Graphic”).

Where in my code it only returned the 1 DOM Element and updated it when the promise resolved.

In practice I suspect its not really an issue…

Yes, I skipped the additional variable to keep the example more concise.

Out of interest: Is there a benefit to returning a generator function from layout(), as opposed to changing the signature to function* layout(layout, parts, ...args)? Or is that snippet stripped down?

Just to save cut / paste code.

FWIW the “simple” working code looks like this (no loading message):

function layout(layout) {
  return function (parts, ...args) {
    const str = parts.map((s, i) => s + (i < args.length ? args[i] : "")).join("");
    
    return graphviz.layout(str, "svg", layout).then(svg => {
      return html`<div>${svg}</div>`;
    });
  }
}

The “complex” working code looks like this (but creates a new div when svg is resolved):

function layout(layout) {
  return function * (parts, ...args) {
    yield html`<div>...loading...</div>`;
    
    const str = parts.map((s, i) => s + (i < args.length ? args[i] : "")).join("");
    
    yield graphviz.layout(str, "svg", layout).then(svg => {
      return html`<div>${svg}</div>`
    });
  }
}

Finally this is closer to what I was shooting for, but the “jump to error” is in the wrong place!

function layout(layout) {
  return async function * (parts, ...args) {
    const div = html`<div>...loading...</div>`;
    yield div;
    
    const str = parts.map((s, i) => s + (i < args.length ? args[i] : "")).join("");
    
    const err = await graphviz.layout(str, "svg", layout).then(svg => {
      div.innerHTML = svg;
    });
    
    if (err !== undefined) {
       yield err;
    }
  }
}

That’s because the Error object’s stack only contains this:

Error: syntax error in line 16 near '-'

    at https://cdn.jsdelivr.net/npm/@hpcc-js/wasm@0.3.14/dist/index.min.js:1:114161

As you can see, layout() is nowhere in that stack. If you want “Jump to error” to point to layout(), you’ll have to throw a new error:

.catch(e => { throw Error(e) })`

… Or did I misunderstand what you meant by “wrong place”?