1

I have an async function that generates a new token if a user's token expires. I copied and pasted the async function code into this project and the class and constructor were already built.

I want to call the async function but I'm not sure how/where to do it, especially with the constructor function present. I can see how and where the other async functions are called (i.e. var _token = await sessionGet(_tokenName)), but since the async function I want to call is the outermost async function (if that makes sense) I'm not sure where I'd call it.

Here's what I know (correct me if I'm wrong):

  • I can't use await outside an async function

  • It's a bad practice to have a constructor function return a Promise

  • My async function has to be async or else it'll "break the flow" of the rest of the code---async is used multiple times throughout

export default class {
  constructor() {
    this.setTokenVar();
    // other function calls
  }

  setTokenVar() {

    async function permissionToCallAPI() { // -------- this is what I want to call
      const _tokenName = "tokenSet",
        _RestHost = "https://.../.../api";
      async function sessionGet(key) {
        let stringValue = window.sessionStorage.getItem(key);
        if (stringValue != null) {
          try {
            const {
              value,
              expirationDateStr
            } = JSON.parse(stringValue);

            if (value && expirationDateStr) {
              let expirationDate = new Date(expirationDateStr);

              if (expirationDate <= new Date()) {
                throw "Expired Token";
              }

              const isValid = await isAuthorized(value.value);

              if (isValid) {
                console.log("Valid Token");
                return value;
              } else {
                throw "Expired Token";
              }
            } // if (value && expirDateStr)
          } catch (e) {
            console.log(e);
            window.sessionStorage.removeItem(key);
          }
        }
        return null;
      }
      async function isAuthorized(key) {
        let ret = false;
        await axios.post(_RestHost + "/User/GenerateToken", null, {
            withCredentials: true,
            async: false,
            headers: {
              "accept": "application/json;odata=verbose",
              "content-type": "application/json",
              "Authorization": key
            }
          }).then(resp => {
            ret = true;
          })
          .catch(err => {
            console.log("Failed Authentication.");
          });

        return ret;
      }

      // add into session
      function sessionSet(key, value, expirationInMin) {
        if (!expirationInMin) {
          expirationInMin = 59;
        }

        var expirationDate = new Date(
          new Date().getTime() + 60000 * expirationInMin
        );

        var newValue = {
          value: value,
          expirationDateStr: expirationDate.toISOString()
        };

        window.sessionStorage.setItem(key, JSON.stringify(newValue));
      }

      var _token = await sessionGet(_tokenName);

      if (_token == null) {
        let request = axios
          .get(_RestHost + "/User/GenerateToken", {
            withCredentials: true
          });

        return request
          .then(response => {

            const authToken = response.data.AuthToken;

            sessionSet(_tokenName, authToken);

            return authToken;
          })
          .catch(err => {
            throw err;
          });
      } else {
        return _token;
      }

    } // permToCallAPI

  } // setTokenVar()
}
Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217
Bodrov
  • 840
  • 1
  • 15
  • 29
  • 1
    Did you have a look at the [alternatives to a promise inside the constructor](https://stackoverflow.com/q/24398699/1048572)? – Bergi Sep 12 '19 at 18:17

1 Answers1

0

Doing anything but setting variables in the constructor is considered an anti-pattern.

The simplest solution is to make the caller responsible for calling obj.setTokenVar() which could then be an async function.

Since the object's state may be inconsistent before that method is called, you could create a factory async function to guarantee that the object is fully initialized before letting callers use it.

export async createThing() {
   const obj = new Thing();
   await obj.setTokenVar();
   return obj;
}

Side Note async can be called from non async functions, but you cannot use await on it, you must use then on the Promise that all async functions return.

function doSomething() {
   createThing().then(thing => console.log(thing))
}

function async doSomething() {
   console.log(await createThing())
}
Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217
  • Hey @juan-mendes. Just to clarify what you said, I should make the `permissionToCallAPI` caller responsible for calling `obj.setTokenVar()`? – Bodrov Sep 12 '19 at 15:05
  • I showed you in the generic sense, it's hard to tell when you copy/paste code that is not relevant and not properly formatted, your code does not seem to make `permissionToCallAPI ` available, seems to be a closure, not accessible outside. I said you should not do heavy work in the constructor. That means the caller would have to call functions themselves. If the object would be in an inconsistent state by not calling some of the functions after you construct it, you could create a factory to make sure all heavy lifting is done before you hand the new object back to the caller. But yes, if – Ruan Mendes Sep 12 '19 at 15:27
  • possibly even better: `async function createThing() { return new Thing(await fetchToken()); }` – Bergi Sep 12 '19 at 18:20
  • @Bergi I didn't do that because `setTokenVar ` doesn't return `this` and I almost suggested it could, but wanted to keep it simpler – Ruan Mendes Sep 13 '19 at 11:58
  • 1
    @JuanMendes I meant to suggest that the `setTokenVar` method can be dropped completely, in favor of static methods (or even local functions inside `createThing`) that can fetch a token - basically the whole `permissionToCallAPI` thing in the OP's code - which can be called *before* the `new Thing` is instantiated. – Bergi Sep 13 '19 at 13:56
  • @Bergi I see and agree. I did not try to to solve the full problem and did not read the full code, I just wanted to give them a general direction so they can figure out how to do it for themselves – Ruan Mendes Sep 13 '19 at 15:05