Home > Mobile >  Can't make function return value wait for fetched result - Now multithreaded
Can't make function return value wait for fetched result - Now multithreaded

Time:02-11

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.

  1. You have a token already
    1. return it
  2. You don't have a token already
    1. make an asynchronous request to get one
    2. when the request resolves store the token
    3. 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;
};
  • Related