DEV Community

Discussion on: The Proper Way to Write Async Constructors in JavaScript

Collapse
 
reinoute profile image
Reinout • Edited

Why still use classes then? If you're using factory functions anyway, you might as well:

const client = await createClient(..)

It seems confusing that you are mixing up two concepts. That would be confusing for other devs in the project (as some classes will not really behave like you would expect).

Also, in the Spotify example, wouldn't it be better to do the fetch first, and pass the result into the constructor? That would avoid this problem all together and there are less dependencies.

Still a creative solution though...

Collapse
 
somedood profile image
Basti Ortiz

Why still use classes then?

Well, the point of using classes is to encapsulate some unit of state. In the Spotify example, this "unit of state" is the resultant access token (from the code exchange). The static async factory function encapsulates the OAuth-related initialization logic, which does not belong outside the client class. The class, after all, is supposed to provide an abstraction over the Spotify API.

Wouldn't it be better to do the fetch first, and then pass the result into the constructor?

If we were to manually exchange the authorization code for an access token and then pass it into the regular constructor ourselves, then this defeats the whole purpose of the abstraction we were going for. The user does not need to know about the details of the token exchange. That's why we've hidden it behind a factory function.

The factory function only serves as an indirection for the actual constructor. From the caller's perspective, all they have to do is pass in an authorization code; the factory function shall put into the private constructor the received access token on your behalf. All other details are abstracted away by the class.

Once constructed, the class may now use the encapsulated access token for API calls in the future, hence the necessity of classes. The access token has to be stored somewhere, after all.

The alternative—as you said—is to return the access token to the user, and let them pass it into the constructor. Again, that just defeats the purpose of the abstraction.

Factory functions alone will not be able to provide the proper abstractions. If we were to use factory functions everywhere instead of classes, then manual orchestration of return values would be inevitable. At least with classes, we would be able to encapsulate the state (i.e., access tokens) so that future invocations will only use the class fields.

It seems you are mixing up two concepts.

I must emphasize that factory functions are only a mechanism for asynchronous initialization. The construction of the object indirectly occurs in the factory function itself since the resultant parameters are just passed into the actual constructor.

Thus, I don't believe I am confusing topics here. A factory function provides an interface for asynchronous construction, where intermediate resultant values (which the user cannot have known beforehand) are used as parameters in the actual construction of the object itself.

For instance, in the Spotify class, the user passes in an authorization code. However, the class constructor requires an access token. The user cannot possibly know this value until the await point of the token exchange. The job of the factory function, then, is to pass in the access token to the constructor on the user's behalf.

That would be confusing for other devs in the project.

I must concede that this is one of the downsides I've mentioned in the conclusion. There is no standardized naming convention for this pattern, therefore documentation is absolutely necessary.

But, I hope in the future, as more developers become familiar with this "async constructor pattern" I have demonstrated, it will become apparent why a class must be designed that way. Just like how we've all studied the Gang of Four patterns, I hope that this will become the de facto pattern for async constructors because I am frankly tired of reading code that uses the other workarounds with regular constructors—which as you've read in the article does not provide good workarounds. 😕

Still a creative solution, though...

Thanks! It really hit me like a stroke a genius one day. I have always thought that there must be a better way of doing things. After looking around some open-source projects with asynchronous construction, I noticed the weird workarounds I wrote about in the article. I've always been baffled.

_ Why not just use this? Why not just move the code here?_

And then I realized... Static async factory functions is the solution! And the next thing you know, this article was born. 😂

Collapse
 
reinoute profile image
Reinout

It's a creative solution for situations where you must use classes. I didn't know about this 'trick' and I would definitely use it if I have to!

But what I'm trying to explain is that classes in JS are just syntactic sugar. What you're essentially doing is modifying the default class functionality to make them behave more like functions. Then why not use normal functions in the first place?

Well, the point of using classes is to encapsulate some unit of state.

You can do exactly that with (normal) factory functions (example here). Including private/public variables and private/public methods. And the best thing, you don't have to worry about this anywhere.

It would be a good excercise to rewrite the Spotify example to use factory functions (not classes) and compare them.

Thread Thread
 
somedood profile image
Basti Ortiz

But what I'm trying to explain is that classes in JS are just syntactic sugar. What you're essentially doing is modifying the default class functionality to make them behave more like functions. Then why not use normal functions in the first place?

Oh, I see! I have slightly misunderstood. If that's the case, I must agree with your point. The static is essentially just a namespace based on my usage. To be fair, though, the namespace does make its relationship with the class explicit.

You can do exactly that with (normal) factory functions.

Indeed! In fact, I have written an article about that years ago as well. The idioms used in that article may be outdated by today's standards, but it's true that factory functions are sufficient for state encapsulation.

From a stylistic perspective, though, I personally prefer using classes for state encapsulation. The class fields make the state more apparent than if we were to use closures and factory functions. Also, classes were designed for this use case, anyway. 😂

Collapse
 
dandv profile image
Dan Dascalescu

There's a cleaner way that sticks with classes and doesn't mix functions in.