skip to Main Content

Since TypeScript is a superset of Javascript and ‘Type’ would be removed in the resulting js files, so I guess playing with ‘type’ would not work like this, right?
Also, is there any better way to do it other than having two different methods?

type VoidToVoidFunc = () => void;
type VoidToPromiseVoidFunc = () => Promise<void>;

async function add(func: VoidToVoidFunc  | VoidToPromiseVoidFunc) {
    if (typeof func == typeof VoidToVoidFunc)
        func(); 
    else
        await func();
}

add(() => console.log(1));

3

Answers


  1. You’re right, you cannot rely on the type since it’s going to be removed once compiled to JavaScript.

    However, you can check the return value of your function. With that, you can check if you should await the promise or simply resolve the value.

    type VoidToVoidFunc = () => void;
    type VoidToPromiseVoidFunc = () => Promise<void>;
    
    async function add(func: VoidToVoidFunc  | VoidToPromiseVoidFunc) {
        /* is either a pending promise or undefined */
        const value = func();
    
        if(value instanceof Promise) {
          await value;
        }
    }
    

    In the case where your function returns a promise, you are await-ing it. And in the case it does not, you are simply skipping the await part.

    The following example is in plain JavaScript, to show you how it works.

    async function add(func) {
        /* is either a pending promise or undefined */
        const value = func();
    
        if(value instanceof Promise) {
          await value;
        }
    }
    
    const asyncFunction = async () => {
      return new Promise(resolve => {
        setTimeout(() => {
            console.log('resolving promised function');
            resolve();
        }, 1000)
      });
    };
    
    const syncFunction = () => {
        console.log('resolving normal function')
    }
    
    add(asyncFunction);
    
    add(syncFunction);
    Login or Signup to reply.
  2. As you can see here, when a non-thenable value follows await, an already-fulfilled Promise is constructed and used.

    So the simplest implementation is to await the result of the function call. Indeed, await wraps a value in a Promise if it’s not already a Promise:

    type VoidToVoidFunc = () => void;
    type VoidToPromiseVoidFunc = () => Promise<void>;
    
    async function add(func: VoidToVoidFunc  | VoidToPromiseVoidFunc) {
      await func();
      // Do something else
    }
    
    add(() => console.log(1));
    

    This behavior is correctly typed by TypeScript, indeed when you write const test = await func(); and hover the variable, its type is correctly inferred to void.

    TypeScript playground

    Login or Signup to reply.
  3. The cleanest and most maintainable solution is the most strait forward

    type VoidToVoidFunc = () => void;
    type VoidToPromiseVoidFunc = () => Promise<void>;
    
    async function add(func: VoidToVoidFunc | VoidToPromiseVoidFunc) {
      await func();
    }
    
    add(() => console.log("sync function", 1));
    
    add(async () => {
      await delay(1000);
      console.log("async function", 2);
    });
    
    
    function delay(ms: number) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }
    

    Playground Link

    async function add(func) {
      await func();
    }
    
    add(() => console.log("sync function", 1));
    
    add(async () => {
      await delay(1000);
      console.log("async function", 2);
    });
    
    function delay(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }

    As you can see from the above, behavior, applying await to a non Promise value simply wraps and unwraps it. As your add function by virtue of being denoted async is necessarily going to return a Promise and run asynchronously (scheduled on the next tick), more complex solutions offer no benefit in your case.

    Check out the async function and Promise docs at MDN for further elaboration.

    Thus, while there are indeed other ways to determine if a you received a callback that may or may not return a Promise, such as

    type VoidToVoidFunc = () => void;
    type VoidToPromiseVoidFunc = () => Promise<void>;
    
    async function add(func: VoidToVoidFunc | VoidToPromiseVoidFunc) {
      const couldBeAPromise = func();
      if (couldBeAPromise && 'then' in couldBeAPromise) {
        await couldBeAPromise;
      }
    }
    
    add(() => console.log("sync function", 1));
    
    add(async () => {
      await delay(1000);
      console.log("async function", 2);
    });
    
    
    function delay(ms: number) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }
    

    Playground Link

    async function add(func) {
      const couldBeAPromise = func();
      if (couldBeAPromise && 'then' in couldBeAPromise) {
        await couldBeAPromise;
      }
    }
    
    add(() => console.log("sync function", 1));
    
    add(async () => {
      await delay(1000);
      console.log("async function", 2);
    });
    
    function delay(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }

    keeping it simple is the way to go here.

    Note: void is a TypeScript type expressing the contract of a function and guiding type inference. However, every JavaScript function and thus every TypeScript function actually returns a value. In this case func param of add returns either undefined or a Promise that resolves to undefined.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search