Typescript: "'Promise' is undefined" error in IE11 even though targeting ES5

Created on 25 Feb 2017  路  18Comments  路  Source: microsoft/TypeScript

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

html<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.
Question

Most helpful comment

@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.

All 18 comments

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.

@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.

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.

@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.

@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.

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

@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.

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>.

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

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.

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

@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.

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

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>

@nsteenbeek Thanks for the solution. Helped me out.

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

@xdvarpunen try babel-polyfill

@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.

Was this page helpful?
0 / 5 - 0 ratings