You can do this synchronously, without promise.
I just tested in Chrome, Firefox, and IE 11,10,9,8, 7 and 5.
It works like this:
function isImportSupported()
{
var supported = false; // do NOT use let
try
{
// No arrow-functions here either ...
new Function("try { import('data:text/javascript;base64,Cg==').catch(function() {}); } catch (e) { }")();
supported = true;
}
catch (e) { }
return supported;
}
Note
Apparently, this doesn't work in Firefox worker process, probably because import throws an error at runtime.
To check this, you actually would have to call the function with await in an async function (fun fact, if you use async, you will break ipad and internet exploder):
async function isImportSupported()
{
var AsyncFunction, fn1, fn2;
try
{
// https://davidwalsh.name/async-function-class
// const AsyncFunction = Object.getPrototypeOf(async function () { }).constructor;
// const fetchPage = new AsyncFunction("url", "return await fetch(url);");
AsyncFunction = new Function("return Object.getPrototypeOf(async function () { }).constructor;")
}
catch (err)
{
return false;
}
try
{
fn1 = new Function("try { import('data:text/javascript;base64,Cg==').catch(function() {}); return true; } catch (e) { }; return false; ");
fn2 = new AsyncFunction("try { await import('data:text/javascript;base64,Cg=='); return true; } catch (e) { }; return false; ");
fn1();
fn2();
// all the above, you can do synchronously.
// But this line can only be done in an async function
await fn2();
}
catch (err)
{
return false;
}
return true;
}
await isImportSupported()
Here's the typescript-definition of IAsyncFunction and IAsyncFunctionConstructor, in case anybody wants it
interface IAsyncFunction
{
(...args: any[]): Promise<any>;
apply(this: IAsyncFunction, thisArg: any, argArray?: any): Promise<any>;
call(this: IAsyncFunction, thisArg: any, ...argArray: any[]): Promise<any>;
bind(this: IAsyncFunction, thisArg: any, ...argArray: any[]): IAsyncFunction;
toString(): string;
prototype: Promise<any>;
readonly length: number;
// Non-standard extensions
arguments: any;
caller: IAsyncFunction;
}
interface IAsyncFunctionConstructor
{
new(...args: string[]): IAsyncFunction;
(...args: string[]): IAsyncFunction;
readonly prototype: IAsyncFunction;
}
The simple truth is, if you want to support those old browsers, don't use ecma modules, and write your own import function.
However, since old browsers don't support promises or async-await, you'll have to polyfill all that, and transpile down to ES5.
My advice is to give up on old browsers and not do anything like this, unless the effort is worth the investment of time (e.g. if you are amazon).
If you are not amazon, simply tell your clients to use a recent browser.
You can block old browsers at the middleware level (e.g. a "sorry your browser is no longer supported" page).
The firefox problem is probably an import implementation along the lines of:
function workerContextImport(data)
{
return new Promise(
function (resolve, reject)
{
let wait = setTimeout(function ()
{
clearTimeout(wait);
if(workerContext)
reject(new Error("Not allowed in worker context!"));
else
resolve(data);
}, 10);
});
}
Here's transpiled ES5 code btw:
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
function isImportSupported() {
return __awaiter(this, void 0, void 0, function () {
var AsyncFunction, fn1, fn2, err_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
try {
AsyncFunction = new Function("return Object.getPrototypeOf(async function () { }).constructor;");
}
catch (err) {
return [2, false];
}
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
fn1 = new Function("try { import('data:text/javascript;base64,Cg==').catch(function() {}); return true; } catch (e) { }; return false; ");
fn2 = new AsyncFunction("try { await import('data:text/javascript;base64,Cg=='); return true; } catch (e) { }; return false; ");
fn1();
fn2();
return [4, fn2()];
case 2:
_a.sent();
return [3, 4];
case 3:
err_1 = _a.sent();
return [2, false];
case 4: return [2, true];
}
});
});
}