Converting external sync function to behave as async

Viewed 93

This is a brainstorming question, looking for ideas.

Is there any method to convert a sync function in an external script to behave like async.

Consider the situation of userscript & userscript manager.

The userscript uses a sync GM_getValue function and userscript developers are reluctant to update the code to async GM.getValue API.

The script manger that is processing the script, can not support sync method.

Is there any way to handle the code in an async way?

Example:

function run() {

  const a = GM_getValue('one');
}
  • Is there a possibility to halt the function by script manager until async response is available?
  • Is there a way to parse the script and convert the relevant functions to async/await? (The Regex is error prone)
  • Is there a way to override the function and replace it with an async version?
    e.g.
async function run() {

  const a = await GM_getValue('one');
}
  • Any other ideas?

Update example subsequent to comments

// ==UserScript==
// @name            My Script
// @match           http://www.example.org/*
// @grant           GM_getValue
// ==/UserScript==

function run() {
  const b = GM_getValue("one"); 
  if (b && b > a) {
    // do somthig
  }
  else {
    // do something else
  }
  return b;
}

const a = 5;
const c = run();
const d = GM_getValue("two"); 
const e = parseInt(d);

if (d > a) {
  // do somthig
}
else {
  // do something else
}

function sum(a, b) {
  return a + b;
}

const f = sum(2, 5);
1 Answers

Is there a possibility to halt the function by script manager until async response is available?

No. The thing that comes closest would be sync XHR, but that's deprecated and should be avoided.

There is no easy one-size-fits-all solution. A regular expression definitely won't be sufficient. You will either have to:

  • Go through the script, identify all uses of the API that needs to be async, and refactor the control flow to account for it. While this might be a bit tedious, it's pretty rote and should be easy for those with experience working with the language.

  • Use a different synchronous method instead. This is what I'd prefer. Instead of using GM_getValue, consider saving the userscript settings in Local Storage instead. This is a usually a good choice unless the data is sensitive and the site is untrustworthy. For example, instead of

    const a = GM_getValue('one');
    

    you could do

    const userscriptSettings = JSON.parse(localStorage.userscriptSettings || '{}');
    const a = userscriptSettings.a;
    

Sometimes local storage won't suffice, such as when saving huge amounts of data, or saving the data where the site has the theoretical ability to change it is something to worry about, in which case you'll have to take the other option involving async control flow refactoring.

For the example in the question, I'd change it to:

const runScript = (userscriptSettings) => {
  function run() {
    const b = userscriptSettings.one;
    // ...
  }
  const a = 5;
  const c = run();
  const d = userscriptSettings.two;
  // ...
};
Promise.all([
  GM.getValue('one'),
  GM.getValue('two'),
])
  .then(([one, two]) => runScript({ one, two }));
Related