-2

I am writing some TypeScript classes that fetch resources by their reference - everyday things like if the resource is a file, the reference is the pathname; if the resource is a web service, the reference is the URL, etc.

In this world, references and resources are always strings.

So I have some code like this:

/**
 * Calls an HTTP endpoint
 */

export default class CallEndpoint {

    getResource(reference: string): string {
        return fetch(reference);
    }

}

I want to define getResource as string, but eagle-eyed readers know I can't: fetch() is async, so getResource can't be defined as returning a string.

I've tried to do some research on this question, and the answer is "properly work with asynchrony as shown in the duplicates linked", which I feel does not answer my question, and brings me to a dead end.

My editor is TypeScript-aware and shows there is an error with the above code. If I do this, it is happy:

export default class CallEndpoint {

    async getResource(reference: string) {
        return await fetch(reference);
    }

}

However, now I am not happy, since I want this to be explicitly marked as returning a string. Indeed, I have written a generic cache wrapper, which I make use of in a child class (CallEndpointCached) and my interface requires this method to return a string (because, well, a resource is always a string). Do I have to make do with this being essentially untyped?

In other words, is there a way I can block in this function, so that I can obtain a string result that respects the function return type I have chosen?

Update

I suppose the async/await is pointless here, but my editor says it still can't be typed. I think it should be:

export default class CallEndpoint {

    getResource(reference: string) {
        return fetch(reference);
    }

}

Something can await this downstream, I guess.

Footnote

(I've bumped into Promises and async several times, and unfortunately I struggle to fully understand them every time. While I am aware of the performance benefits of async, the stall on developer productivity strikes me as more than sapping those benefits, especially where the system is not in need of optimisation).

halfer
  • 19,824
  • 17
  • 99
  • 186
  • In terms of the benefits, if a *synchronous* request fails, hangs, or you don't have a timeout mechanism it will *freeze the page*, because there's only one thread. So no, synchronous requests *cannot* be allowed to come back. Ever. No amount of lost developer productivity is worth the user headache, and it has nothing to do with optimization. – Jared Smith Apr 16 '21 at 16:48
  • @JaredSmith: this is for a Node API - my use case is my endpoint shall be called via HTTP and it will make an onward call to a different API. However, Stephen indicates below that it is still required, in order to ensure the Node server can be called by other users. – halfer Apr 16 '21 at 16:53
  • @JaredSmith: my post might be a dup, but I am not sure the link above is a very good one. The question is a mishmash of browser JS and Node JS (which normally would make it suitable for closure IMO), and I am not sure any of the answers deal with the question of TypeScript return types. I confess I only waded through the first page of answers though... – halfer Apr 16 '21 at 17:02
  • It's *even worse* in Node, because instead of *one* user having a frozen page *all of your users* have an unresponsive server, which is why Node won't let you do that (the browser makes you jump through hoops). Also you are correct, a better dupe target is [here](https://stackoverflow.com/questions/41103360/how-to-use-fetch-in-typescript). – Jared Smith Apr 16 '21 at 17:05
  • Thanks @JaredSmith, that's perfect. So I think I have to learn generics now... `:=)` – halfer Apr 16 '21 at 17:09
  • Yup, no way around that in TS. Too many containers (Array, Promise, Map, Set, Components, etc). – Jared Smith Apr 16 '21 at 17:22
  • Ha, I'll change my emoticon to `:=(` ! – halfer Apr 16 '21 at 17:27

1 Answers1

3

async methods always return promises. Any return value is wrapped in a Promise<T>. So if you have a method that returns a value of type string, then the return value of that method is Promise<string>.

especially where the system is not in need of optimisation

async does not mean "faster". It means "responsive". Asynchronous code is required on the UI side for the browser window to remain responsive (avoid the "frozen tab" dialog), and it's required on the Node.js side for the server to remain responsive (handle more than one request at a time).

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Ah, thanks - can I use `Promise` as an explicit return type, and not just in `@return` docblocks? – halfer Apr 16 '21 at 16:46
  • @halfer yes you can and indeed must – Jared Smith Apr 16 '21 at 16:47
  • @halfer: Yup. `getResource(reference: string): Promise` – Stephen Cleary Apr 16 '21 at 16:47
  • Right, great stuff. I think my question feels like a curveball that I need to process, so my initial questions are probably silly ones. But I will ask anyway: if I create a new class that fetches a resource for a reference and can do so synchronously, then in order to obey the interface I have (not shown in the question) that would also have to return `Promise`. Would it be TypeScriptonic for me to wrap the result in a Promise even though it can return a result straight away? (If this is an involved query, I can post a new question). – halfer Apr 16 '21 at 16:50
  • @halfer: Anything doing I/O will need to be asynchronous. If you have a test stub or something then use `Promise.resolve(value)`. – Stephen Cleary Apr 16 '21 at 16:55