0

So I have the following problem:

Given a key I will get a function from a dictionary, this function can be async or normal function. But when I put the await keyword before func the compiler complains:

TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'Promise ' has no compatible call signatures.

How could I solve this issue? A switch would be the only solution?

Simple example:

function example(aux: string){
    return string
}

function async main(key, ...args): Promise<Boolean>{
    dictionary = {
        "example": example
    }

    let func = dictionary[key]

    if (func instanceof Promise) { // If the function is a promise we wait
          let result = await func(...args) //<-- PROBLEM HERE
       } else {
          let result = func(...args)
       }
    }

    return result

Full example here:

export enum ValidatorsTypes {
  EMAIL = 1,
  EMAIL_IS_UNIQUE = 2
}

export function validEmail (text: any) {
  const pattern = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
  return pattern.test(text) || 'Invalid e-mail.'
}

export async function isEmailUnique (text: any): Promise<boolean> {
  let response = await UserService.list(1, 1, { 'email': text })

  // if the number of matches is 0, then is unique
  return response[1] === 0
}

export async function isAnswerValid (validatorsTypes: Array<ValidatorsTypes>, ...args: any) : Promise<boolean> {
  let availableValidators = {
    [ValidatorsTypes.EMAIL]: validEmail,
    [ValidatorsTypes.EMAIL_IS_UNIQUE]: isEmailUnique
  }

  let results = []

  for (let rule of validatorsTypes) {
    let func = availableValidators[rule]

    if (func instanceof Promise) { // If the function is a promise we wait
      let result = await func(...args) //<-- PROBLEM HERE
      results.push(result)
    } else {
      results.push(func(...args))
    }
  }

  return !results.some(v => v === false)
}
1
  • 1
    Aron has provided the solution. Let me just say that I think you should decide whether you really want to run through all the rules synchronously (i,e. not moving on until the current rule, which may be async, has finished). If so I would suggest you return early if something comes back false (and then you can avoid storing this results array. Otherwise I would suggest you send off all the things 'at the same time', and either they all come back true, or one comes back false and you return false and cancel all the other things (if you can). Commented Jun 11, 2020 at 1:59

2 Answers 2

2

See the edit below for a much simpler solution


The problem is that func instanceof Promise checks whether func is itself a Promise, not whether a function that returns a promise.

If you want to do different things based on func's return value you need to do the instanceof Promise check on what it returns.

So something like this would work

const result = func(...args);
// const result: true | 'Invalid e-mail.' | Promise<boolean>

if (result instanceof Promise) { // we check if result is a Promise
  const awaitedResult = await result; // if result is a promise we await it
  // const awaitedResult: boolean

  results.push(awaitedResult);
} else {
  results.push(result); // otherwise we just push the value immediately
}

EDIT

As @Countingstuff pointed out there's no harm in awaiting non-promises so we can simplify this whole thing by just awaiting whatever func returns, there is no need to check whether anything is a promise at all!

const func = availableValidators[rule];
// const func: ((text: any) => true | "Invalid e-mail.") | ((text: any) => Promise<boolean>)

const result = await func(...args);
// const result: boolean | 'Invalid e-mail.'

results.push(result);
Sign up to request clarification or add additional context in comments.

1 Comment

There's no harm in awaiting something that's not a promise, so actually I think you can remove the whole conditional.
1

Your function has an Any type when you grab it out of the dictionary. You have 2 options, first you can cast is as Function e.g.

let func = dictionary[key] as Function;

or instead of using an object as your dictionary, use a Map<string, Function> e.g

const dict = new Map<string, Function>();
dict.set("example", this.example);
let func = dict.get('example');

After you have your func, you can just await func() it, no need for the if check

4 Comments

How does this answer the question?
You see in op's question where there is a comment //<-- PROBLEM HERE. That is how this is answering the question.
The problem is how we can conditionally await a function based on whether it returns a promise or not. How does your answer help with that?
That's not what the problem is. The problem is TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'Promise ' has no compatible call signatures. How could I solve this issue?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.