2
\$\begingroup\$

I have a function that takes in a string and based on the string hits different APIs to fetch images.

I have solved it by writing an interface and then as the return type of the function, it returns a Promise<Interface> as shown in the code below.

    interface IAnimal {
      image?: string | null
      message?: string
      status: string
    }
    const getAnimal = async (inAnimal: string): Promise<IAnimal> => {
      const isDogWord: boolean = ["dog", "Dog", "DOG"].includes(inAnimal.trim())
      const isFoxWord: boolean = ["fox", "Fox", "FOX"].includes(inAnimal.trim())
      let result: IAnimal = { image: null, status: "tofetch" };
      if(isDogWord) {
        result = await fetch("https://dog.ceo/api/breeds/image/random").then((data) => data.json());
        if(result.status === "success") {
          return {
            image: result.message,
            status: result.status
          }
        }
      }
      if(isFoxWord) {
        result = await fetch("https://randomfox.ca/floof/").then((data) => data.json());
        return {
          image: result.image,
          status: "success"
        }
      }
      return {
        image: null,
        status: "failure"
      }
    };

This solves the typescript errors but I am not happy with making the image and message optional in the Interface. I am receiving { "message": "image__link", "status": "success or failure" } from dog API and {"image": "image__link", "status": "success or failure"} from the fox API. Is what I have done the right approach or do we have a better solution.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

The problem here is that the interface IAnimal is not what those apis return. You want to return that interface from your function, but those APIs should have their own interface representing the response data.

interface DogApiResponse {
  message: string
  status: string
}

interface FoxApiResponse {
  image: string
}

interface Animal {
  status: string
  image: string | null
}

To generalize the set of available animal types I propose to also add interface for the fetching part of individual APIs.

declare type RandomAnimalFactory = () => Promise<Animal>

Now we declare two factories - for dogs and foxes

const dogFactory: RandomAnimalFactory = async () => {
  const result: DogApiResponse = await fetch(dogApiUrl).then(data => data.json())
  if (result.status !== 'success') {
    throw new Error('Dog API railure')
  }
  return {image: result.message, status: 'success'}
}

const foxFactory: RandomAnimalFactory = async () => {
  const result: FoxApiResponse = await fetch(foxApiUrl).then(data => data.json())
  return {image: result.image, status: 'success'}
}

The we can have the named animal factory work like this

declare type NamedAnimalFactory = (name: string) => Promise<Animal>


const createRandomNamedAnimalFactory = (randomFactories: {[key: string]: RandomAnimalFactory}): NamedAnimalFactory => {
  return async (name) => {
    const factory = randomFactories[name.trim().toLowerCase()]
    if (!factory) {
      return {image: null, status: 'failure'}
    }

    try {
      return await factory()
    } catch {
      return {image: null, status: 'failure'}
    }
  }
}

const getAnimal: NamedAnimalFactory = createRandomNamedAnimalFactory({
  dog: dogFactory,
  fox: foxFactory,
})
```
\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.