3

So I have a function that should immediatly return a rejected or resolved Promise, i.e. it is basically a snychronous function I want to "promisify".

What I usually do in such cases is something like this:

func() {
     // some code to check for an error
     if (hasError)
        return Promise.reject(new Error("Failure"));
     }
     return Promise.resolve("Success");
}

Now, with the availability of "async" functions in ES2017 it seems like I could also do this:

async func() {
     // some code to check for an error
     if (hasError)
        throw new Error("Failure");
     }
     return "Success";
}

So I basically use async just to "promisify" my function, without using await in the function body anywhere. As I see it, this variant should be doing exactly the same. Am I right, or are there any additional side effects I am not aware of here?

I think I would prefer this pattern, as it is a bit shorter, it is very clear from the function definition alone that it is async, and any JavaScript errors (like type errors) would also lead to a rejection which makes my overall code react more gracefully in case of unexpected errors.

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
frontend_dev
  • 1,693
  • 14
  • 28
  • *"As I see it, this variant should be doing exactly the same."* correct *"Am I right, or are there any additional side effects I am not aware of here?"* nope, nothing unexpected here. – Thomas Jun 02 '18 at 19:05
  • related: https://stackoverflow.com/q/47911388/1048572, https://stackoverflow.com/q/45788934/1048572 – Bergi Jun 02 '18 at 20:05
  • [You should **never** return a promise from a synchronous function](https://stackoverflow.com/q/47699235/1048572)! – Bergi Jun 02 '18 at 20:05
  • 1
    Bergi: why? While the function is synchronous in nature, I want it to _behave_ asynchronously. The reason is that I have an API that unifies all kinds of storage operations. Some of my adapters are async (XHR), some of them are not (localStorage). But I want to have a unified API, so that I always can do things like myStorage.read().then(...), no matter if the adapter I am using is async or not. Still not a usecase you would accept? – frontend_dev Jun 02 '18 at 20:36
  • ... so the API as such is async in nature. Now, it is _much_ easier for the main class to handle results from any adapter in the same way, i.e. expecting a promise. I also can do things like reading from multiple sources at once by using Promise.race, there is support for caching (even with multiple adapters) etc. So I would dare to say: never say "never" ;) – frontend_dev Jun 02 '18 at 20:47
  • @Bergi There's quite a difference between "not always" and "never". The accepted answer on linked question says "If you have a function that performs an asynchronous operation _or may perform an asynchronous operation_, then it is reasonable and generally good design to return a promise from that function" (emphasis mine). So, no, definitely not "never". – Frax Jun 03 '18 at 08:55
  • @frontend_dev I'd dispute calling it "shorter". `return Promise.resolve(...)` is a very clean pattern, and while longer in characters, reads as easily as a single keyword. I'd say it's more readable. And the issue is, when I want to see what function returns, I usually look on return statements, and I think yours may be confusing (and `async` keyword is pretty far and easy to miss). Though that's the matter of taste, and if you are consistent, your version is also fine. – Frax Jun 03 '18 at 09:24
  • @frontend_dev I guess it's fine if you program against an interface that requires a promise. However, you could also make your `localStorage` API be synchronous, and just have the consumer of the API accept both synchronous and asynchronous variants (by simply `await`ing them). – Bergi Jun 03 '18 at 13:21
  • @Frax Sure, never say never, but "*a function that may perform an asynchronous operation*" is not a "synchronous function" in my book. – Bergi Jun 03 '18 at 13:22
  • @Frax, yeah ultimately it may be a matter of taste / style. Still prefer having the `async` keyword, as then you don't have to look at the function body at all to see how this function behaves. Also, since I tend to have a kind of generic error message in my `catch` handler anyways, also things like type errors effectively result in feedback to the user that something went wrong, and I think thats better than something silently failing. Therefore I would say the code is more resilient in general using this technique. – frontend_dev Jun 03 '18 at 16:00
  • @Bergi, with my solution you don't access any adapter directly, they act more like a kind of plugin that you can configure. Of course I could make some adapters synchronous, but then I would have to "promisify" this anyways in my main storage class. The point is also that an API user usually does not care where the request came from. So if you use caching, the response _might_ be coming from the XHR or fetch adapter, _OR_ from memory, localStorage etc. depending on the setup. Now, if an API user had to differentiate if a request returns a promise or not, this would lead to an ambigious API. – frontend_dev Jun 03 '18 at 16:06

1 Answers1

4

Whenever you declare an async function an AsyncFunction object is created, which according to MDN will return a Promise:

A Promise which will be resolved with the value returned by the async function, or rejected with an uncaught exception thrown from within the async function.

So yeah it using async will promisify a function.

Also, let's do some testing;

async function fn(val) {
  console.log(val);
  if (val) {
    return "true";
  }
  throw new Error("false");
}

console.log(Object.getPrototypeOf(fn).constructor)

console.log(fn(true) instanceof Promise);
console.log(Object.getPrototypeOf(fn(true)).constructor)

console.log(fn(false) instanceof Promise);
console.log(Object.getPrototypeOf(fn(false)).constructor)

fn(true).then(val => console.log(val));

fn(false).catch(err => console.log("ERROR"));
J. Pichardo
  • 3,077
  • 21
  • 37