Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"'Promise' is undefined" error in IE11 even though targeting ES5 #14308

Closed
devuxer opened this issue Feb 25, 2017 · 18 comments
Closed

"'Promise' is undefined" error in IE11 even though targeting ES5 #14308

devuxer opened this issue Feb 25, 2017 · 18 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@devuxer
Copy link

devuxer commented Feb 25, 2017

TypeScript Version: 2.1.1

Code

tsconfig.json

{
    "compilerOptions": {
        "target": "es5",
        "lib": [
            "es2016",
            "dom"
        ],
        "module": "commonjs",
        "declaration": false,
        "noImplicitAny": false,
        "removeComments": true,
        "experimentalDecorators": true,
        "noLib": false,
        "jsx": "react",
        "outDir": "dist"
    },
    "exclude": [
        "node_modules",
        "static"
    ]
}

ajax.ts

export class Ajax {
    static async getJson<TArray>(url: string) {
        const request = new XMLHttpRequest();
        return new Promise<TArray[]>(resolve => {
            request.onreadystatechange = () => {
                if (request.readyState != XMLHttpRequest.DONE || request.status != 200) return;
                const data = JSON.parse(request.responseText);
                resolve(data);
            };
            request.open("GET", url, true);
            request.send();
        });
    }
}

index.tsx

import { Ajax } from './utilities/ajax';
import { Holiday } from './models/Holiday';
import { Country } from './models/Country';
import { NationList } from './components/NationList';
import { AppState } from './models/AppState';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { HolidayList } from "./components/HolidayList";

initialize();

async function initialize() {
    const countries = await Ajax.getJson<{ id: string, name: string }>('src/data/countries.json');
    const holidays = await Ajax.getJson<{ id: number, countryId: string, date: Date, day: string, name: string }>('src/data/holidays.json');
    const appState = new AppState(countries, holidays);
    ReactDOM.render(
        <div>
            <NationList appState={appState} />
            <HolidayList appState={appState} />
        </div>,
        document.getElementById("root")
    );
}

index.html

  <head>
    <title>Scheduling Assistant</title>
    <!--<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.5/bluebird.min.js"></script>-->
  </head>
  <body>
    <div id="root"></div>
    <script src="/static/bundle.js"></script>
  </body>
</html>

Expected behavior

  • Promise is replaced with callbacks, etc. to be compatible with ES5.
  • Runs without error in IE11.

Actual behavior

  • Fails in IE 11 with error SCRIPT5009: 'Promise' is undefined.
  • Only works in IE 11 if bluebird.min.js script element is uncommented.
@gcnew
Copy link
Contributor

gcnew commented Feb 25, 2017

The runtime has to support Promise or you must provide a polyfill. See the explanation

Note: first, we need to make sure our run-time has an ECMAScript-compliant Promise available globally. That might involve grabbing a polyfill for Promise, or relying on one that you might have in the run-time that you're targeting.

@devuxer
Copy link
Author

devuxer commented Feb 26, 2017

@gcnew, Thanks for the quick response. I had read that note in the docs, but I didn't really "get it" at the time. Now that I've experienced it, it makes sense.

Anyway, it's a little confusing to me what exactly it means when I put "target": "es5" in compilerOptions. I used to think it meant that typescript would generate code that works in an ES5 browser, period. Now, it seems that there are occasional examples where this isn't the case, and you basically have to carefully read the docs to find those out (or infer it from browser errors).

An interesting counter-example to Promise not working, BTW, is that Symbol does seem to get polyfilled (at least, I get no error in IE 11 when putting code containing Symbol() in the same .ts file as I put Promise()).

So, I'm not seeing a clear rule of thumb on when the typescript compiler will polyfill something and when it won't. I think it would be very helpful to developers if all ES6 language features and types were polyfilled when specifying "target": "es5". The way it works now is a violation of the principle of least astonishment IMO.

@Jessidhia
Copy link

Jessidhia commented Feb 27, 2017

It is possible to emit perfectly fine es5 (or even es3) code for async/await that will work as long as the environment provides a Promise polyfill.

However, it would be nice to have a way of forbidding the compiler from emitting code that may require certain polyfills; maybe throwing a syntax error about forbidden syntax, similar to how TypeScript complains about for of with non-string/array types in es3 targets.

@blakeembrey
Copy link
Contributor

@Kovensky Doesn't the lib option and compiler errors already do that? You set the lib option to what you know exists, add other globals you know exist and the compiler does the rest.

@gcnew
Copy link
Contributor

gcnew commented Feb 27, 2017

@devuxer Are you sure you environment doesn't support Symbol or some of your dependencies doesn't bring a shim in? I've tried the new asyncInterator downstream emit and it didn't polyfill Symbol at all.

@Jessidhia
Copy link

The lib option is only for typechecking, it doesn't affect codegen.

@mhegazy mhegazy added the Question An issue which isn't directly actionable in code label Feb 27, 2017
@blakeembrey
Copy link
Contributor

@Kovensky Exactly. You were asking for something that:

nice to have a way of forbidding the compiler from emitting code that may require certain polyfills

That's the compilers goal. It would error if you didn't include the Promise types. So if you want to support only ES5 environments, you should set lib to es5 and use the polyfills to "upgrade" the environment.

@Jessidhia
Copy link

Note that not everything is polyfillable; you may also be working with a partial implementation. For ES5, say, Array.prototype.every is easily polyfillable, but Object.defineProperty is impossible except in the specific case of enumerable+writable+configurable value properties. For ES6, Object.assign is also easily polyfillable, but good luck doing anything about Proxy.

Though I guess that, in this case, it could work to have the compiler refuse to accept async if it can't generate the return type for the function. If you don't include at least es2015.promise, it's impossible to annotate the function's return type, as you can't use Promise<T>, but the compiler requires that it be a Promise<T>.

@unional
Copy link
Contributor

unional commented Feb 28, 2017

One idea is when hook is available, a tool may be created alongside tsc to install the polyfills you need for the specific target. 🌷

@blakeembrey
Copy link
Contributor

Note that not everything is polyfillable; you may also be working with a partial implementation.

Absolutely. I think in the ideal world the type information would possibly block the impossible things, but I also realise it's pretty improbable to build that out since most people will pick up the default lib anyway. Luckily that's basically the point though - no type info = compiler error.

Yeah, in the async case, I believe it's handled already because the compiler will error if the Promise value and type does not exist.

One idea is when hook is available, a tool may be created alongside tsc to install the polyfills you need for the specific target.

That could be a nice idea. Combine that with browser statistics information should be pretty easy too so that only the polyfills on each supported environment are included.

@Maximaximum
Copy link

Well, the issue has been closed, but I haven't really found the answer to the question here :(

@devuxer
Copy link
Author

devuxer commented Apr 16, 2018

@Maximaximum,

The answer for me has been to visit polyfill.io anytime I get a runtime error in IE11. The error almost always gives a hint as to what feature is missing, and polyfill.io almost always has a polyfill for it.

@Maximaximum
Copy link

@devuxer Thanks for the summary, it's really helpful!

@nsteenbeek
Copy link

When jsconfig.json target is 'es5' and lib has 'es6' following should 'fix' all IE11 polyfills:

<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=es6"></script>

@shaeebSP
Copy link

@nsteenbeek Thanks for the solution. Helped me out.

@xdvarpunen
Copy link

I need solution where I have no access to polyfill.io (no internet access)

@nsteenbeek
Copy link

@xdvarpunen try babel-polyfill

@Pelleson
Copy link

Pelleson commented Apr 8, 2019

@nsteenbeek If ever meet you in person i'm gonna give you a large hug and buy you a beer, your solution helped me out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests