Multiple listeners to single promise

Viewed 286

I have a module with an asynchronous function init() which is invoked multiple times. However, I only want the effect of the init() function to trigger once and any subsequent calls should just wait for that initial promise to resolve.

Here is my current code. The error in this example is that the second time the init() function is called, it is immediately resolved since the script tag already exists. I would like to wait for the onload callback from the script created on first init() invocation.

let scriptExists = false;

// This function should only be called once
// Returns a mock script that calls onload after 1000ms
function addScript() {
  const mockScript = {
    onload: () => {}
  };
  setTimeout(() => mockScript.onload(), 1000);
  return mockScript;
}

// Add script and wait for it to load
function init() {
  return new Promise((resolve) => {
    if (!scriptExists) {
      const script = addScript();
      scriptExists = true;
      script.onload = resolve;
    } else {
      // TODO: This should only resolve if the script has loaded
      resolve();
    }
  });
}

// Wait for the script to load, then add message
async function doSomethingNice() {
  await init();
  console.log("nocie");
}

// Wait for the script to load, then add message
async function doSomethingElse() {
  await init();
  console.log("something else");
}

doSomethingNice();
doSomethingElse();

https://codesandbox.io/s/modest-cohen-9k4bf?file=/src/index.js

3 Answers

You can set up your init function so that it only does the work once, and on subsequent calls it returns the promise from the first time:

let promise;
function init() {
  if (!promise) {
    promise = new Promise((resolve) => {
      const script = document.createElement('script');
      script.id = SCRIPT_ID;
      script.onload = resolve;
      script.type = 'text/javascript';
      script.src = 'https://www.example.com/';
      document.head.appendChild(script);
    });
  }
  return promise;
}

Here is a working example, I think if you use it in the context of a class it will be a better code pattern.

function init() {
  return new Promise((resolve) => {
    console.log("run");
    if (!document.getElementById("script")) {
      const script = document.createElement("script");
      script.id = SCRIPT_ID;
      script.onload = resolve;
      script.type = "text/javascript";
      script.src = "https://www.example.com/";
      document.head.appendChild(script);
    } else {
      resolve();
    }
  });
}

var loader = undefined;

async function doSomethingNice() {
  !loader ? (loader = init()) : await loader;
  console.log("nice");
}

async function doSomethingElse() {
  !loader ? (loader = init()) : await loader;
  console.log("something else");
}

doSomethingNice();
doSomethingElse();

So basically we do not have any Native API to check the state of the promise, So what we can do is to have a variable and check them with another function that returns a Promise .

I was not able to completely use your sample code, but this is a working example, Given that a promise resolves, we move on to the other call.

let PromiseState = false;
const promiseStateTimer = 1000;

// this function is responsible to manage the state
function checkState() {
  return new Promise((resolve) => {
    const checkInterval = setInterval(() => {
      // console.log(PromiseState);
      if (!PromiseState) {
        clearInterval(checkInterval);
        resolve(PromiseState);
      }
    }, promiseStateTimer);
  });
}


function init() {
  // change state ------------------- !
  if (!PromiseState) {
    PromiseState = !PromiseState;
  }
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // revert back the state while resolving -------------------- !
      PromiseState = !PromiseState;
      resolve("Hello");
    }, 3000);
  });
}

async function doSomethingNice() {
  await checkState();
  await init();
  console.log("something nice");
}


async function doSomethingElse() {
  await checkState();
  await init();
  console.log("something else");
}

doSomethingNice();
doSomethingElse();

As per the promiseStateTimer, the state of the PromiseState is checked for every one second, and then the values are resolved, But you still need to add in some patch work to your listened Promise to make them work.

Related