1

I'm using io.js, a fork of node that already supports ES6 generators without the need for special flags, and Kris Kowal's Q library.

What I'm making is a game script, where almost every single action is async, and I'm using Q.spawn to keep things sane. This is the current state of my code, and it works:

var q = require('q');
var tw = require('./lib/typewriter');
q.spawn(function*() {
    tw.clear();
    yield tw.type({d:100}, "August 3, 9:47 AM", {w:500});
    yield tw.type("District Court");
    yield tw.type("Defendant Lobby No. 2", {w:2000});
    yield tw.breakLine();
    yield tw.type({who:"Phoenix"}, {dh:true}, {d:30}, "(Boy am I nervous!)", {w:1500});
    yield tw.breakLine().then(function(){ throw new Error("BOOM!"); });
    yield tw.type({who:"Mia"}, {dh:true}, {d:40}, "Wright!", {w:1250});
    yield tw.type({di:true}, {d:50}, "Did you", {w:1000}, {d:0}, " ", {d:30}, "turn off the lights?", {w:1000});
    yield tw.type({di:true}, {d:400}, ". . .", {w:1000});
    yield tw.type({di:true}, {d:40}, "I can't see a thing!", {w:1000});
    yield tw.breakLine();
    process.exit();
});

However, adding yield to every single line sucks. I'm almost making the jump towards Luvit to escape from this madness, but I'm giving JavaScript its chance.

Under normal circumstances, I can omit most yielding, like this:

var q = require('q');
var tw = require('./lib/typewriter');
q.spawn(function*() {
    tw.clear();
    tw.type({d:100}, "August 3, 9:47 AM", {w:500});
    tw.type("District Court");
    tw.type("Defendant Lobby No. 2", {w:2000});
    tw.breakLine();
    tw.type({who:"Phoenix"}, {dh:true}, {d:30}, "(Boy am I nervous!)", {w:1500});
    tw.breakLine();
    tw.type({who:"Mia"}, {dh:true}, {d:40}, "Wright!", {w:1250});
    tw.type({di:true}, {d:50}, "Did you", {w:1000}, {d:0}, " ", {d:30}, "turn off the lights?", {w:1000});
    tw.type({di:true}, {d:400}, ". . .", {w:1000});
    tw.type({di:true}, {d:40}, "I can't see a thing!", {w:1000});
    yield tw.breakLine();
    process.exit();
});

Only one yield is still there, just to be sure process.exit() won't execute too early. The typewriter module actually queues most commands, so this works. This would be reasonable.

However, if a callback throws somewhere, as in:

tw.breakLine().then(function(){ throw new Error("BOOM!"); });

Then Q will swallow it up and, since there is no catch handler attached to that promise, it will just be silently garbage collected.

If JavaScript generators just detected expression statements yielding promises and automatically yielded that, it would be awesome (so long as you could opt out of it somehow, of course).

Is there a JavaScript preprocessor that does that?

Or is there some other way to avoid explicitly yielding on every single line but still get exceptions to be caught?

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Gui Prá
  • 5,559
  • 4
  • 34
  • 59
  • Why would you have "*a callback that throws somewhere*" at all? Why not yield if you use a callback? Or is it `tw` itself that fails? – Bergi Apr 21 '15 at 10:59
  • Many things can cause an exception to be thrown, for example a bug whereby an undefined property is called as a function throws a TypeError. I just don't want those things to go unreported, basically, and they can happen virtually anywhere. – Gui Prá Apr 21 '15 at 21:11
  • Yeah, but if the bug is in the `tw` library you're basically caught off guard, and cannot do much. It would the library's responsibility to reject the returned promises, and also promises that depend on it if there is internal queuing. – Bergi Apr 21 '15 at 23:21
  • They would either propagate into `Q.spawn()` or into the promise it was called from's rejection chain. If it propagates into `Q.spawn()`, good, it will throw. In the latter case, it would be just like any other error in the resolve chain (it would be swallowed). So I think it would be the same... – Gui Prá Apr 21 '15 at 23:29

1 Answers1

0

If JavaScript generators just detected expression statements yielding promises and automatically yielded that, it would be awesome.

It would be terrible.

Or is there some other way to avoid explicitly yielding on every single line but still get exceptions to be caught?

Not really. But for your use case I would recommed to use an asynchronous helper function to decrease the number of yields to use:

var paragraph = q.async(function*(lines) {
    for (let line of lines)
        yield tw.type(...line);
    yield tw.breakLine();
});
q.spawn(function*() {
    tw.clear();
    yield paragraph([
        [{d:100}, "August 3, 9:47 AM", {w:500}],
        ["District Court"],
        ["Defendant Lobby No. 2", {w:2000}]
    ]);
    yield paragraph([
        [{who:"Phoenix"}, {dh:true}, {d:30}, "(Boy am I nervous!)", {w:1500}]
    ]);
    throw new Error("BOOM!");
    yield paragraph([
        [{who:"Mia"}, {dh:true}, {d:40}, "Wright!", {w:1250}],
        [{di:true}, {d:50}, "Did you", {w:1000}, {d:0}, " ", {d:30}, "turn off the lights?", {w:1000}],
        [{di:true}, {d:400}, ". . .", {w:1000}],
        [{di:true}, {d:40}, "I can't see a thing!", {w:1000}]
    ]);
    process.exit();
});
Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I don't know. I agree that synchronous control flow guarantees are important and usually what one would want, but I think automatic yielding might be interesting, if only as an experiment. If I knew how to make preprocessors that didn't mess up line / column offsets in error reporting, I'd really want to give it a try. – Gui Prá Apr 21 '15 at 21:31
  • One thing still bothers me about this: If I accidentally forget to yield, my code will work, but exceptions will be swallowed by Q. Since this is a game and this library doesn't really produce recoverable errors, I'll make `tw` attach a handler that makes the exception leak. If they happen, the whole game crashes, but that's better than silently ignoring them... – Gui Prá Apr 21 '15 at 22:13
  • Meh, that doesn't work. `Q.done()` does just that, but I can't make sure client code (outside of `tw`) will use `.done()` on its own promises. So I would still have to remember to call it every time, or yield. I really hope the community finds a way to make this safer... :( – Gui Prá Apr 21 '15 at 22:33
  • Well, advanced promise libraries use unhandled rejection tracking. Afaik Q does support it? – Bergi Apr 21 '15 at 23:14
  • I wasn't aware of that. I do think Q has it. However I must choose a place where I say "okay, if there are still unhandled errors at this point, throw them". I would still have to explicitly do that, but I guess it's something. Thanks! – Gui Prá Apr 21 '15 at 23:20