How to do asynchronous operation in object constructor
Objects are a good way to encapsulate data. You provide an interface to the consumer without revealing the internal state and implementation details. Sometimes you'd like to make an asynchronous call while initializing the object, but it's not that straightforward to make it happen.
How do you make an asynchronous call in object constructor?
As an example, we'll create a class that represents a web page. It accepts a url in the constructor and it provides two methods: getting the contents of the web page and calculating a hash of the contents.
Contract for object constructor prevents returning Promise
How a constructor works is, when you call new WebPage("url")
the constructor is executed with a fresh new object {}
is set as this
. The constructor modifies the object setting and initializing variables. Finally, as the constructor returns, that object becomes the result of the new WebPage("url")
expression.
Since the new
-expression returns the newly created object, returning a Promise is not an option. Therefore it's not possible to perform an asynchronous call to its completion during this process.
There are few approaches to work around this.
Approach 1 - Start call in constructor, await completion in method
Since performing an asynchronous call to its completion in the constructor is not an option, we can still start a call in the constructor. We'd start it in the constructor, save the unsettled Promise in an instance variable, and then await for its completion in the methods that need it.
We'll wait for the Promise's completion in all the places we need the result. The first method to be executed will do the actual waiting, and any subsequent calls will use the already resolved value.
Important: Need to call method in same tick
Theres one catch. If the call throws an exception, you'll get an unhandled promise rejection unless you call one of the methods during the same tick.
Approach 2 - Use async initializer() method
Another approach is to dedicate the constructor for initializing variables and have a separate asynchronous method that performs the asynchronous calls. Then, your constructor would not start any calls, only the initializer method would.
The user could call the initializer method manually.
Or, the initializer method could be internal and act as a guard at the beginning of each method. In this approach, we'd also have cover the possibility of concurrent calls causing the initializer to be called multiple times. The initializer methods are prefixed with underscore (_
) to demarkate they're not part of the public interface.
Approach 3 - Use asynchronous factory function
This is similar to using initializer method, but we're not exposing the constructor ever to be called on its own. Instead, we'd provide an asynchronous function that both instantiates the object and calls the initializer. This could be called a factory function.
In this case it's helpful if we can hide the visibility of the constructor and prevent it from ever being called directly. It'd be only called through the factory method.
Semantic Versioning Cheatsheet
Learn the difference between caret (^) and tilde (~) in package.json.