1

I'm not optimistic there's a way to do this, but I wanted to put the question out there. The situation: I'm writing a JavaScript game engine that's tailored for novice programmers. I want the primary scripting interface to be as simple and accessible as possible. Ideally, scripts would end up looking something like this:

this.backdrop('backdrop-1.png', { effect: 'fade-in', duration: 1000, sync: true });
this.backdrop('backdrop-2.png', { effect: 'fade-in', duration: 500 });

In this scenario, the first backdrop would fade in completely over the course of one second, then the second backdrop would fade in synchronously. The problem? JavaScript will try to execute the second command before the first has completed.

Now, the right way to handle this would be to use promises. Something like:

this.backdrop('backdrop-1.png', { effect: 'fade-in', duration: 1000 }).then(() => {
  this.backdrop('backdrop-2.png', { effect: 'fade-in', duration: 500 });
});

Which is totally an option. (In fact, it's probably what I'll have to settle for.) But there are two reasons why I'd prefer not to go this route. First, it adds a lot of extra verbiage to what is meant to be a simple script. I'd like this to look unintimidating to new programmers, and all those nested functions are gonna start looking scary. Secondly, promises don't handle control flow super well. Even something as simple as an if-then statement will get gnarly.

What I'd really like is some way to do this:

this.backdrop('backdrop-1.png', { effect: 'fade-in', duration: 1000, sync: true });
waitUntilAllPromisesAreResolved();
this.backdrop('backdrop-2.png', { effect: 'fade-in', duration: 500 });

But that's a pipedream, isn't it? If you can think of a way to make that real, I'd love to hear it.

nullnullnull
  • 8,039
  • 12
  • 55
  • 107
  • 1
    Perhaps you can use HTML5 worker threads http://stackoverflow.com/questions/30036/javascript-and-threads/30891727#30891727 to handle this situation – HashPsi Jul 28 '15 at 17:21
  • Oh, that's interesting. I had completely forgotten about workers! I'll look into that more. – nullnullnull Jul 28 '15 at 17:21
  • @HashPsi So are you thinking something like: run `this.backdrop` on a worker, then freeze the main script in a while loop until `this.backdrop` has resolved? I haven't tried anything like that before, and I'm a little wary of putting the primary thread to sleep. Still, it might be the best option here. – nullnullnull Jul 28 '15 at 17:26
  • Thinking of it a bit more, it may not work as the worker will most likely not be able to modify the DOM (since DOM access is not thread safe). – HashPsi Jul 28 '15 at 17:31
  • You could have the async version of `backdrop` perform it work in chunks with each chunk run `setTimeout`. `waitUntilAllPromisesAreResolved` would listen to events generated by `backdrop` and return only when `backdrop` is done. Keeping the UI responsive may be somewhat tricky though. – HashPsi Jul 28 '15 at 17:35
  • Yeah, that's the problem I'm running into now. If `waitUntilAllPromisesAreResolved` uses a while loop to freeze the main thread, the whole application becomes unresponsive. – nullnullnull Jul 28 '15 at 17:43

1 Answers1

2

it adds a lot of extra verbiage to what is meant to be a simple script. I'd like this to look unintimidating to new programmers, and all those nested functions are gonna start looking scary.

Hm, it's much better than a script where this is done by magic (which certainly possible, see below). Most newbies will be able to figure out what then does, but they won't be able to recognise magic - and are totally confused when something doesn't work then.

Btw, you should almost never have to nest functions when using promises over callbacks, as promises form a flat chain.

Secondly, promises don't handle control flow super well. Even something as simple as an if-then statement will get gnarly.

They do it better than most other options imo. See this example of a simple if-statement.

What I'd really like is some way to do this:

this.backdrop('backdrop-1.png', { effect: 'fade-in', duration: 1000, sync: true });
waitUntilAllPromisesAreResolved();
this.backdrop('backdrop-2.png', { effect: 'fade-in', duration: 500 });

But that's a pipedream, isn't it?

Not necessarily - there are generators and eventually there will be async/await. Those allow you to use "synchronous" (normal) control-flow statements in asynchronous functions:

(async function() {
    await this.backdrop('backdrop-1.png', { effect: 'fade-in', duration: 1000});
    await this.backdrop('backdrop-2.png', { effect: 'fade-in', duration: 500 });
    console.log("both done");
}()); // returns a promise

You'll have to use an ES6/ES7 transpiler for these, though.

However, even a simple

this.backdrop('backdrop-1.png', { effect: 'fade-in', duration: 1000, sync: true });
this.backdrop('backdrop-2.png', { effect: 'fade-in', duration: 500 });

where the second effect is animated after the first is very possible today! You just have to put an internal queue of animations on this, to which the backdrop method adds instead of executing immediately. jQuery does use this approach for example, and it's very popular!

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Bergi, you're a champ! The project is already using Babel, so it's pretty easy to integrate this. – nullnullnull Jul 28 '15 at 18:52
  • Great answer! I would call it an internal *chain* rather than a queue, since it would be a sequence of *asynchronous* operations, where one starts when the previous one ends. Such a chain could even be implemented with promises. – jib Jul 29 '15 at 00:56
  • @jib: I'd say it depends on the implementation (capabilities, visibilities, who manages it) whether it looks more like a queue or more like a chain. But yes, you could do it with a promise chain. – Bergi Jul 29 '15 at 12:27