-
Notifications
You must be signed in to change notification settings - Fork 48
Asynchronous read and write migration
This page covers the general and full impact on users of GM_config due to the switch to asynchronous reading and writing of stored values. This is a direct result of supporting the Greasemonkey 4+ APIs. Why did your script seem to be working just fine in Greasemonkey? GM_config had been falling back to localStorage
in Greasemonkey, so it wouldn't seem like there was an impact.
Firstly, remember to use the proper @grant
lines in the your script metadata:
// ==UserScript==
// @name Script Name
// @namespace Script Namespace
// @require https://openuserjs.org/src/libs/sizzle/GM_config.js
//
// Legacy GM3 support
// @grant GM_getValue
// @grant GM_setValue
// Current GM4 support
// @grant GM.getValue
// @grant GM.setValue
// ==/UserScript==
Note: Code examples use initialization via the constructor, but GM_config.init
is still fully supported.
Initialization is now done asynchronously, which means this will no longer work:
let gmc = new GM_config(
{
'id': 'ThisConfig',
'fields': {
'val': {
'label': 'Value',
'type': 'text'
}
}
});
// initialization is not complete
// value is not yet available
let val = gmc.get('val');
Now get
needs to be moved into the init
event callback function:
let gmc = new GM_config(
{
'id': 'ThisConfig',
'fields': {
'val': {
'label': 'Value',
'type': 'text'
}
},
'events': {
'init': () => {
// initialization complete
// value is now available
let val = gmc.get('val');
}
}
});
Another way to write this is:
let gmc = new GM_config(
{
'id': 'ThisConfig',
'fields': {
'val': {
'label': 'Value',
'type': 'text'
}
},
'events': {
'init': onInit
}
});
function onInit() {
// initialization complete
// value is now available
let val = gmc.get('val');
}
Finally, using a Promise
:
let gmc = new GM_config(
{
'id': 'ThisConfig',
'fields': {
'val_1': {
'label': 'Value 1',
'type': 'text'
},
'val_2': {
'label': 'Value 2',
'type': 'text'
}
}
});
// Promise resolves when initialization completes
let onInit = config => new Promise(resolve => {
let isInit = () => setTimeout(() =>
config.isInit ? resolve() : isInit(), 0);
isInit();
});
// Generate a Promise
let init = onInit(gmc);
// Break up get() calls
init.then(() => {
// initialization complete
// value is now available
let val = gmc.get('val_1');
});
init.then(() => {
// initialization complete
// value is now available
let val = gmc.get('val_2');
});
Extra Example: break up get
calls without a Promise
// Register multiple callbacks as a single callback function
function Callbacks() {
let hasRun = false;
let runables = [];
let next = () => runables.shift();
let run = function () {
hasRun = true;
for (let fn = next(); !!fn; fn = next())
setTimeout(fn);
};
run.add = fn => {
runables.push(fn);
if (hasRun) run();
};
return run;
}
// Get a single callback function
let onInit = new Callbacks();
let gmc = new GM_config(
{
'id': 'ThisConfig',
'fields': {
'val_1': {
'label': 'Value 1',
'type': 'text'
},
'val_2': {
'label': 'Value 2',
'type': 'text'
}
},
'events': {
'init': onInit
}
});
// Break-up get() calls
onInit.add(() => {
// initialization complete
// value is now available
let val = gmc.get('val_1');
});
onInit.add(() => {
// initialization complete
// value is now available
let val = gmc.get('val_2');
});
This section covers impact on advance usage. Most users can just skip this.
Potential impact on advance usage:
-
getValue
,setValue
,log
,read
,write
,save
, andinit
are all asynchronous now (open
was already asynchronous).-
setValue
,log
,write
, andsave
can be used like before in most cases. -
getValue
,read
, andinit
are impacted the most. - I consider
getValue
,setValue
,log
,read
, andwrite
to be internal so I don't count them as a breaking change.
-
-
save
andinit
are the official API functions that are affected by a breaking change, even thoughsave
won't be impacted in most usages. - Although not asynchronous,
get
fails when used beforeinit
has finished.
The async execution paths are: init
-> read
-> getValue
and save
-> write
-> setValue
. Both getValue
and setValue
use a Promise, whereas read
and write
use callback functions (last argument).
Note: init
refers to initialization that can be reach via: GM_config.init
, GM_config
, new GM_config
, GM_configStruct
, new GM_configStruct
, configInstance.init