Previously I asked this question in a single threaded situation, and was given an answer that worked in that situation here. But now I'm trying to answer the same question in a multithreaded environment.
I'm using OpenLayers to draw a UK Ordnance Survey map. I have working examples, but they use a less secure method of accessing the UKOS servers by giving a key in the URL, whereas I'd like to use the more secure OAuth2 arrangement instead. To this end, I already have a CGI script querying the servers to get a session token, and returning this as JSON. The problem can be solved in a single-threaded environment by making the map drawing page wait for its return, as follows:
UKOS.getToken = async function () {
if (
!UKOS.token ||
new Date().getTime() >
UKOS.token.issued_at 1000 * (UKOS.token.expires_in - 15)
)
UKOS.token = await fetch(/* CGI Script */).then((response) =>
response.json()
);
console.log(UKOS.token.access_token);
return UKOS.token.access_token;
};
// ...
UKOS.getToken()
.then(token => fetch(UKOS.WMTS "&request=GetCapabilities&version=2.0.0", { headers: {Authorization: "Bearer " token} }) )
.then( /* etc */ )
However, now that I'm using a multi-threaded environment the above no longer works, because after the first call to getToken() launches the fetching of the token, subsequent calls come crashing through before it can be returned, and, depending on exactly how I try to code getToken(), either launch multiple fetches of tokens when one will do (until it expires), or else don't wait for the token to be returned, and fail because it is still null. This is my current attempted adaptation of getToken() ...
UKOS = {
token: null,
tokStat: 0,
tokProm: null
}
UKOS.getToken = async function() {
var result;
console.log( "UKOS.tokStat: " UKOS.tokStat );
switch( UKOS.tokStat ) {
case 1: await UKOS.tokProm;
await UKOS.token;
// Deliberate fall through
console.log( "UKOS.token: " UKOS.token );
console.log( "Falling through from 1 to 2 ..." );
case 2: if( new Date().getTime() < UKOS.token.issued_at 1000*(UKOS.token.expires_in - 15) ) {
result = UKOS.token.access_token;
break;
}
// Deliberate fall through
console.log( "Falling through from 2 to 0 ..." );
case 0: UKOS.tokStat = 1;
UKOS.tokProm = fetch( location.protocol '//' location.host "/cgi-bin/OSDataOAuth.py" );
UKOS.token = await UKOS.tokProm.then( response => response.json() );
UKOS.tokStat = 2;
result = UKOS.token.access_token;
}
return result;
};
Without the two 'await' qualifiers, of course subsequent calls just crash through into 'case 2' and fail because the token is still null, with them, this is the result [irrelevant detail edited out] ...
// Debugging console log message from routine calling getToken()
url: https://api.os.uk/maps/raster/v1/wmts?service=WMTS&REQUEST=G…TRIX=EPSG:27700:0&TILEROW=6&TILECOL=1&FORMAT=image/png
// Debugging console log message from getToken()
UKOS.tokStat: 0
url: https://api.os.uk/maps/raster/v1/wmts?service=WMTS&REQUEST=G…TRIX=EPSG:27700:0&TILEROW=5&TILECOL=1&FORMAT=image/png
UKOS.tokStat: 1
[Repeated multiple times]
url: undefined
url: https://api.os.uk/maps/raster/v1/wmts?service=WMTS&REQUEST=G…TRIX=EPSG:27700:0&TILEROW=6&TILECOL=1&FORMAT=image/png
[Repeated multiple times]
url: https://api.os.uk/maps/raster/v1/wmts?service=WMTS&REQUEST=G…TRIX=EPSG:27700:0&TILEROW=6&TILECOL=2&FORMAT=image/png
UKOS.tokStat: 1
// Token fetched successfully
// (see end, the only map tile fetch that succeeds is almost certainly the first to call getToken())
XHRGET http://local.localhost/cgi-bin/OSDataOAuth.py
[HTTP/1.1 200 OK 1590ms]
url: https://api.os.uk/maps/raster/v1/wmts?service=WMTS&REQUEST=G…TRIX=EPSG:27700:0&TILEROW=5&TILECOL=2&FORMAT=image/png
UKOS.tokStat: 1
[Repeated multiple times]
UKOS.token: null
Falling through from 1 to 2 ...
[Repeated multiple times]
Falling through from 1 to 2 ...
Uncaught (in promise) TypeError: UKOS.token is null
getToken http://local.localhost/JavaJive/ProgScriptWeb/UKOSOpenLayers2.shtml:206
setImgSrc http://local.localhost/JavaJive/ProgScriptWeb/UKOSOpenLayers2.shtml:301
initImage https://openlayers.org/api/2.13/OpenLayers.debug.js:29733
renderTile https://openlayers.org/api/2.13/OpenLayers.debug.js:29608
draw https://openlayers.org/api/2.13/OpenLayers.debug.js:29581
initGriddedTiles https://openlayers.org/api/2.13/OpenLayers.debug.js:30951
moveTo https://openlayers.org/api/2.13/OpenLayers.debug.js:30422
moveTo https://openlayers.org/api/2.13/OpenLayers.debug.js:83261
moveTo https://openlayers.org/api/2.13/OpenLayers.debug.js:9273
setCenter https://openlayers.org/api/2.13/OpenLayers.debug.js:9035
setBaseLayer http://local.localhost/JavaJive/ProgScriptWeb/UKOSOpenLayers2.shtml:442
addLayer https://openlayers.org/api/2.13/OpenLayers.debug.js:8354
initOSMap http://local.localhost/JavaJive/ProgScriptWeb/UKOSOpenLayers2.shtml:457
onl oad http://local.localhost/JavaJive/ProgScriptWeb/UKOSOpenLayers2.shtml:1
UKOSOpenLayers2.shtml:206:34
[Repeated multiple times]
// I think this one, the only one to successfully fetch a map tile is probably the first attempt to do so, which correctly waited for the returned token
XHRGET https://api.os.uk/maps/raster/v1/wmts?service=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=Leisure_27700&STYLE=default&TILEMATRIXSET=EPSG:27700&TILEMATRIX=EPSG:27700:0&TILEROW=6&TILECOL=1&FORMAT=image/png
[HTTP/1.1 200 OK 1417ms]
dataUrl: blob:http://local.localhost/2f7fa455-3385-4d10-ab0e-b70999658137
What is interesting about the above is that the two 'await's do appear to be accomplishing something, but not enough, because without them each ...
UKOS.tokStat: 1
... is immediately followed by ...
UKOS.token: null
Falling through from 1 to 2 ...
... yet they're obviously not waiting for the token to be returned, as evidenced by the fact that they all fail before the first call ultimately succeeds. Can anyone suggest how to make the second and subsequent calls wait for the token requested by the first call?
CodePudding user response:
In your original code there are two paths through the function.
- You have a token already
- return it
- You don't have a token already
- make an asynchronous request to get one
- when the request resolves store the token
- return it
When you call the function twice in rapid succession, you fall through the gap between 2.1 and 2.2.
Stop storing the token. Store the promise returned by 2.1 instead.
That way, if a request is in-flight you can reuse it.
UKOS.getToken = async function () {
if (UKOS.tokenPromise) {
const token = await UKOS.tokenPromise;
const now = new Date().getTime();
if (now <= token.issued_at 1000 * (token.expires_in - 15)) {
// Token still good
return token.access_token;
}
// If we get here, the token has expired
}
// If we get here, the token has expired or we didn't have a promise in the first place
// Create or update token promise
UKOS.tokenPromise = fetch(/* CGI Script */).then((response) => response.json());
// Wait for it to resolve
const token = UKOS.tokenPromise;
// Assume the new token is good
return UKOS.token.access_token;
};