Trying to wrap the new GM4 asynchronous methods into synchronous methods. What am I doing wrong?

665 views
Skip to first unread message

Stig Nygaard

unread,
Oct 8, 2017, 10:12:17 AM10/8/17
to greasemonkey-users
I am trying to wrap GM4's asynchronous methods into synchronous methods for a fast and easy way to make my current userscripts "cross-API compatible".

I'm not familiar with asynchronous Javascript, but by reading som posts on subject I thought I had found out how to achieve what I wanted. But it doesn't work :-/ ...

When running below code in GM3 it alerts "value=42" (as expected). But when running on GM4 it alerts "value=[object Promise]".
What am I doing wrong? How do I get rid of that Promise?...

[ I have trouble posting in this forum. Maybe it doesn't like the code I'm posting or my posts get to big? So now posting a link to code instead: ]

https://gist.github.com/StigNygaard/ea03b7ad2faeabe732c308786e3046c9

I know it would be better to try to take advantage of the possibilities of asynchronous code, but that must be a project for a later time. Right now I'm just looking for the quick and easy fix.

/Stig.

Stig Nygaard

unread,
Oct 8, 2017, 10:14:54 AM10/8/17
to greasemonkey-users
Okay, maybe I have more luck posting code in a comment(?):

var gm4test = {
    INFO: true,
    DEBUG: false,
    log: function(s, info) {
        if ((info && window.console) || (gm4test.DEBUG && window.console)) {
            window.console.log('*GM4test* '+s);
        }
    },
    getValue: function(name, defval) {
        var rt;
        if (typeof GM_getValue === 'function') { // GM3 etc
            rt = GM_getValue(name, defval);
        } else if (typeof GM === 'object' && typeof GM.setValue === 'function') { // GM4
            rt = (async function(){return await GM.getValue(name, defval);})();
        } else {
            alert('Sorry, no support for GM getValue API method');
            gm4test.log('Sorry, no support for GM getValue API method', gm4test.INFO);
        }
        return rt;
    },
    setValue: function(name, value) {
        if (typeof GM_setValue === 'function') { // GM3 etc
            GM_setValue(name, value);
        } else if (typeof GM === 'object' && typeof GM.setValue === 'function') { // GM4
            (async function(){await GM.setValue(name, value);})();
        } else {
            alert('Sorry, no support for GM setValue API method');
            gm4test.log('Sorry, no support for GM setValue API method', gm4test.INFO);
        }
    },
    run: function () {
        gm4test.log('Running...');
      
        // Set...
        gm4test.setValue('gm4val', 42);
        // and get again...
        var rv = gm4test.getValue('gm4val', '');

        alert('value='+rv); // Expected "value=42", but with GM4 I get "value=[object Promise]" !
    }
};
gm4test.run();

RodMcguire

unread,
Oct 8, 2017, 2:06:33 PM10/8/17
to greasemonkey-users
From the definition of an asynchronous function it is impossible to convert it into a synchronous function other than in the most brutal fashion as follows:

Lets say synF calls asynF and asynF on completion sets some value that synF can see. All synF has to do is go into an infinite loop that polls that value to see if it has changed and exits when it has.

I'm not sure if this approach will actually work in Firefox's JavaScript. It may be that asynF will never get a chance to run because the system is busy running synF's infinite loop.

Dave Cooliave

unread,
Oct 9, 2017, 1:56:46 AM10/9/17
to greasemonkey-users

Basically, your getValue method is returning two types of values; either your desired value or a Promise. Since you're using the asyncronous GM API, you should discard the old API because there's no way to make the two work the same without dealing with Promises.

The async keyword "promisifies" functions, meaning they'll return Promises, so the following two functions are the same:
async function myFunc() { ... }
function myFunc() { return new Promise(...) }

Promises either resolve or reject with a value. To get the value just call the Promise's then method.
var promise = myFunc()
promise
.then(value => ...)

If I were to rewrite your code, it would be as follows:
rt = await GM.getValue(name, defval);
Message has been deleted

Dave Cooliave

unread,
Oct 9, 2017, 2:04:24 AM10/9/17
to greasemonkey-users
My message gets cut off for some reason. Here's the last part:
var gm4test = {
   
...
    getValue
: async function(name, defval) {

       
var rt;
       
if (typeof GM_getValue === 'function') {

            rt
= GM_getValue(name, defval);
       
} else {

            rt
= await GM.getValue(name, defval);
       
}

       
return rt;
   
},
   
...
}

Stig Nygaard

unread,
Oct 9, 2017, 2:33:57 AM10/9/17
to greasemonkey-users
Hi Dave

But then I'm back to a method returning a Promise (which is what I was trying to avoid) ? Can I easily get that value out of the Promise and continue with it in a existing unaltered "synchronous script"? I can't see how, so I think you are basically saying like Rod, it is impossible to convert an asynchronous function into a synchronous function (in a nice way) ?

Dave Cooliave

unread,
Oct 9, 2017, 5:29:42 AM10/9/17
to greasemonkey-users
My bad, Stig. I should have read that thoroughly. Rod is right. You're not supposed to convert the async functions.

Why not run an async function that fills a storage object and then run your unaltered sync code? Perhaps this:

((init) => {
   
// run function.
   
// waits for the init function to finish before running the script.
    init
.then(([run, ...args]) => {
        run
(...args)
   
}).catch(e => {
        alert
("Caught an error: " + e.toString())
   
})
})
((async (main) => {
   
// init function.
   
// uses async GM API for initialization.
   
// returns our sync API.
   
var storage = {}

   
// fetch async values and fill the storage
    await
Promise.all([
        GM
.getValue('gm4val', '').then(v => storage['gm4val'] = v),
        GM
.getValue('foo', '').then(v => storage['foo'] = v),
        GM
.getValue('bar', '').then(v => storage['bar'] = v),
   
]);


   
// our sync API

   
var gm4test = {
        INFO
: true,
        DEBUG
: false,

        log
(s, info) {

           
if ((info && window.console) || (gm4test.DEBUG && window.console)) {
                window
.console.log('*GM4test* '+s);
           
}
       
},

        getValue
(name, defval) {
           
var rt = name in storage ? storage[name] : defval;
           
return rt;
       
},
        setValue
(name, value) {
            storage
[name] = value
            GM
.setValue(name, value).catch(e => gm4test(`Couldn't set value: ${name} ${value}`))
       
}
   
};


   
return [main, gm4test];
})
((gm4test) => {
   
// ==UserScript==
   
// @name        GM4 API test
   
// @namespace   dk.rockland.userscript.gm4apitest
   
// @description Testing cross-API getValue and setValue
   
// @version     2017.10.08.0
   
// @author      Stig Nygaard, http://www.rockland.dk
   
// @match       *://*.greasespot.net/*
   
// @grant       GM.getValue
   
// @grant       GM.setValue
   
// @grant       GM_getValue
   
// @grant       GM_setValue
   
// ==/UserScript==



    gm4test
.log('Running...');


   
// Set...
    gm4test
.setValue('gm4val', 42);
   
// and get again...
   
var rv = gm4test.getValue('gm4val', '');


    alert
('value='+rv); // Expected "value=42"
}));

It sounds like you're going to have to choose a GM API flavor and rewrite your user scripts.

Stig Nygaard

unread,
Oct 9, 2017, 5:27:50 PM10/9/17
to greasemonkey-users
Hi Dave

Thanks very much for the thoughtful idea/example.

For a start I think I will make cookie or web storage based "synchronous" version of getValue/setValue. My scripts are usually "single-domain" anyway, so it should work and be pretty easy to implement in my current scripts.

But later, I plan to take some time to get more familiar with asynchronous javascript and take a second look at your example. But right now, I must admit it's a little bit over my head. Just looking at those "arrow function expression" is a bit confusing when you are not used to use those yourself.

But thanks again. Really appreciated. I hope to find time to get back to it.


Anthony Lieuallen

unread,
Oct 10, 2017, 11:16:25 AM10/10/17
to greasemon...@googlegroups.com
On Sun, Oct 8, 2017 at 2:06 PM, 'RodMcguire' via greasemonkey-users <greasemon...@googlegroups.com> wrote:
From the definition of an asynchronous function it is impossible to convert it into a synchronous function other than in the most brutal fashion as follows:

I've been traveling/busy so just skimming, but this seems to be the heart of the issue.  The expected path forward is: update scripts to use async calls, provide a polyfill whereby legacy/sync APIs back an async equivalent.  NOT DONE yet, but so far this is: https://arantius.com/misc/greasemonkey/imports/greasemonkey4-polyfill.js  -- See the comment at the top there for more detail.

Reply all
Reply to author
Forward
0 new messages