How can multiple unconnected async events await a single promise

Viewed 252

I have a system where I need an id from a server to handle events. I should only fetch the id if/when the first event happens, but after that, I need to use the same id for each subsequent event. I know how to use async-await etc. so I have some code like this

var id = "";
async function handleEvent(e) {
    if (! id ) {
        let response = await fetch(URL)
        if (response.ok) { 
            let json = await response.json();
            id = json.id ;
        }
    }
    // use id to handle event
}

But my problem is that I could receive multiple events before I receive a response, so I get multiple overlapping calls to fetch a new id.

How can I have multiple asynchronous calls to handleEvent, with the first one processing the fetch and any subsequent call waiting for it to complete to access the result?

4 Answers

Create a function to ensure you only make one request for the id using a lazy promise.

const URL = 'whatever'
let idPromise // so lazy 

const getId = async () => {
  const response = await fetch(URL)
  if (!response.ok) {
    throw response
  }
  return (await response.json()).id
}

const initialise = () {
  if (!idPromise) {
    idPromise = getId()
  }
  return idPromise
}

// and assuming you're using a module system
export default initialise

Now all you have to do is prefix any other call with initialise() to get the ID which will only happen once

import initialise from 'path/to/initialise'

async function handleEvent(e) {
  const id = await initialise()

  // do stuff with the ID
}

The currently accepted response relies on a global variable, which is not ideal. Another option is to use a class.

class IDManager {
    getId(URL) {
        if (this.id) {
            return this.id;
        }
        this.id = fetch(URL)
        return this.id
    }
}

Then when you call getId, you simply await the result. If no previous request has been made, a network request will be sent. If there is already a pending request, every call will await the same result. If the promise is already resolved, you will get the result immediately.

const idManager = new IDManager();
async function handleEvent() {
   const id = await idManager.getId(URL);
   // Do stuff with the ID.
}

Not clear why your function is parameterized by "e". I would write it in a more straightforward manner:

async function request(URL) {
       let response = fetch(URL)
        if (response.ok) { 
            let json = await response.json();
            return json.id;
        }
        return false;
}

Then if the sequence of your calls matters write them one by one. If not then you could use Promise.all (Promise.allSettled maybe) to run them all at once. https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

The solution to this turned out to be a little different from the previous answers. I thought I would post how I made it work in the end. The answers from @phil and @vaelin really helped me to figure this out.

Here was my solution...

class IDManager {
    async fetchID (resolve,reject ) {
        const response = await fetch( URL, { } ) ;
        const id = await response.json() ;
        resolve( id );
    }
    async getID() {
        if ( this.id === undefined ) {
            if ( this.promise === undefined ) {
                var self = this;
                this.promise = new Promise( this.fetchID ).then( function(id) { self.id = id;} );
            }
            await this.promise;
        }
        return this.id;
    }
}

The problem was that awaiting the fetch the getID call took a couple of seconds. During that time there were often multiple calls to getID, all of which initiated another fetch. I avoided that by wrapping the fetch and response.json calls in another promise which was created instantly, and so avoided the duplicates.

Related