20

Here's one way to detect if the current engine supports async functions:

const supportsAsyncFunctions = (() => {
  try {
    new Function('async () => {}')();
  } catch (error) {
    return false;
  }

  return true;
})();

But is there a way to do it without using eval or Function?

callum
  • 34,206
  • 35
  • 106
  • 163
  • Don't think so, any reason for wanting it? – Xotic750 Apr 19 '17 at 17:42
  • 1
    I think you could probably rewrite your question title to be something along the lines of "How to detect new syntax features without eval". This doesn't limit itself to just `async`; it also affects checking for arrow shortcuts etc. – zero298 Apr 19 '17 at 18:08
  • 2
    What were you planning to do, or not do, if async/await were or were not available? –  Apr 19 '17 at 18:12
  • 2
    @zero298 there's probably no general way to solve that wider problem, but there might be a specific trick for detecting certain features, e.g. checking for a certain property that only exists on engines that also support async functions. I dunno. – callum Apr 19 '17 at 18:55
  • 1
    @torazaburo one option would be choosing whether to load a version of the app that uses async functions vs. a different version that is compiled down to ES5 for older browsers. (Better to use real async functions where possible, for performance, bundle size, and easier debugging of runtime exceptions.) – callum Apr 19 '17 at 18:59
  • The pattern that you have there is the general way to detect such features. – Xotic750 Apr 20 '17 at 03:09
  • 4
    I did some research and, as it was already mentioned, that's the correct way of checking such things, thru `eval`. The `AsyncFunction` constructor isn't available straight away: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction You need to create an async function first and then extract the constructor from it. Also such words like 'await' or 'async' aren't protected or reserved, so you can even do `const await=1` or `let async=2` – Oskar Apr 20 '17 at 11:36

2 Answers2

12

Suggested eval way will give false negatives for CSP errors because they are not handled. If this is a problem, CSP errors can be handled as shown in this answer.

It is possible to do that, but the solution is not pretty at all and involves external script. The script can set a flag, or global error handler can be set up to watch for syntax errors when the script is loaded.

If the script results in syntax error, it is still considered loaded, so network problems cannot cause false positives:

async-detection.js

window.isAsyncAvailable = true;
async () => {};

main.js

new Promise(function (resolve, reject) {
  var script = document.createElement('script');
  document.body.appendChild(script);
  script.onload = resolve.bind(null, true);
  script.onerror = reject;
  script.async = true;
  script.src = 'async-detection.js';
})
.then(function () {
  console.log(window.isAsyncAvailable);
})
.catch(/* generic script loading error */);

This method can be used to detect syntactic features used on the website that cannot be polyfilled or normally caught with try..catch. It is particularly useful to reliably trigger Your browser is outdated nag screen.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
2

Extending upon the brilliant ideas in @Estus Flask's answer...

var div = document.createElement("div");
div.innerHTML = '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==" onload="async () => {}; window.isAsyncAvailable = true;">'

This creates a DIV in memory, then appends an IMG within it that uses a Base64 Encoded Pixel while also leveraging this trick to run the eval so that you don't need to define the external script nor have an external image to load. This can also be simplified into a one-liner:

document.createElement("div").innerHTML = '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==" onload="async () => {}; window.isAsyncAvailable = true;">';

However, this runs afoul of some CSP configurations. If this pertains to you, then @Estus Flask's external script is your go-to.

Campbeln
  • 2,880
  • 3
  • 33
  • 33