google / traceur-compiler

Traceur is a JavaScript.next-to-JavaScript-of-today compiler
Apache License 2.0
8.18k stars 580 forks source link

Promise.all with a generator #1955

Open baileyparker opened 9 years ago

baileyparker commented 9 years ago

The Promise polyfill as of 1951203 supports Promise.all() on any iterables (including generators compiled by Traceur). This is a great feature!

Unfortunately, a problem arises when you try to pass a traceur-compiled generator to a native implementation of Promise (notably Safari >=7.1 supports Promises, but not generators). It seems that the native Promise.all does not understand Traceur's generators so it silently fails. This is of course very difficult to debug if you don't know what to look for (the Promise never rejects because the input collection is assumed to be empty).

Because of the order that browsers supported Generators and Promises, this problem occurs elsewhere:

Perhaps Safari >= 7.1 isn't the most important given its small market share, but Chrome 32-39 has about 2.5-3% usage according to CanIUse.com. I think this is a significant enough segment to warrant a fix.

Is the best approach here to polyfill native Promise.all() whenever generators are also not native? That feels wrong, but is there another sane way given that Safari >=7.1 doesn't support the new iterator protocol (and nor does Chrome until 38)?

arv commented 9 years ago

Not sure. One simple work around is to turn the iterator into an array before passing it into Promise.all.

baileyparker commented 9 years ago

Right. That's the quickfix I'm using right now. So that it only applies to problematic browsers, I'm using (for others' reference who are experiencing this issue):

var generator        = generateSomething(),
    nativePromises   = Promise.toString().indexOf('[native code]') !== -1,
    nativeGenerators = (function() {
        try {
            eval('(function *() {})');
            return true;
        } catch(err) {
            return false;
        }
    })();

if(nativePromises && !nativeGenerators) {
    generator = Array.from(generator);
}

Promise.all(generator)
    // etc ...

But, this isn't very maintainable if generators are being used for lists that would normally be too large to fit comfortably in memory. This is, of course, one of their intended uses. I'll defer to you (Promise pun totally intended), though, @arv about the best way to proceed beyond this band aid solution.

arv commented 9 years ago

What it comes down to is how much code are you willing to ship to these platforms? It seems like you might want to not use the iterator version of promises if you are planning to do a mobile web app for example.

To enforce this you could do a dev time assert that you are only passing in arrays to Promise.all.