How to do asynchronous operation in object constructor

How to do object-oriented programming (OOP) with asynchronous calls? An object constructor is responsible for initializing the state of the object, but async calls don't fit the equation well. How to approach this?

"How to do an asynchronous operation in a constructor?"

Let's approach this problem with the tried and true principle: separation of concerns.

Constructor sets only variables

The constructor function is best left to set internal state at the variable level without making any calls, be it synchronous or asynchronous calls. This is destined by the mechanics of constructors - the newly created object is returned as the result of a constructor function. This rules out returning a Promise as a return value. One alternative would be to accept a callback parameter, but it would lead to quite unorthodox code. You'd have to assign the result of a constructor and then refer to itself inside the callback given to itself. Not a very elegant solution. It's best to leave constructor for setting variables only and use separate initializer method to perform asynchronous duties.

Dedicated initialize() method for async

A separate initializer method (such as init() or initialize() ) can be used to set up the object state. It would then use as many asynchronous calls as necessary to get the object properly set up. This way there is a separation of setting up variable values in the constructor and interacting with external parts in the initializer method.

The responsibility of calling the initializer method can be thought two ways.

  • You can leave it for the user of the object. You can require that the initializer is called before any operation or otherwise the behavior is undefined.
  • The other alternative is to keep the initializer method not part of the public API. Instead, you call it under the hood at the beginning of each public method. In this case, it's a good idea to add guard logic to only perform anything on the first call and let it pass through on subsequent calls.

An example with web resources

Let's create an example that utilizes an initializer method. We'll create a class called WebResource that represents a URL and its contents. The constructor accepts only one argument: the URL of the resource. Two other methods are provided to get the contents and calculate a hash of the contents.

Here we've chosen to use the hidden initializer approach. Every public method of the API begins with a call to the internal initializer method. It'll only do work on the first call so it won't be overhead.

Bluebird's coroutines are used to ease the pain of dealing with multiple asynchronous operations.

1 var Promise = require('bluebird');
2 var request = 
require('request-promise');
3 var crypto = require('crypto');
4 
5 function WebResource(url) {
6     this.url = url;
7 }
8 
9 WebResource.prototype.initialize = Promise.coroutine(function* () {
10     if (!this
.data) {
11         this.data = yield request(this.url);
12     }
13 });
14 
15 WebResource.prototype.hash = Promise.coroutine(function* () {
16     yield this.initialize();
17 
18     var hash = crypto.createHash('sha256');
19     hash.update(this.data);
20     return hash.digest('hex');
21 });
22 
23 WebResource.prototype.contents = Promise.coroutine(function* () {
24     yield this.initialize();
25 
26     return this.data;
27 });
28 
29 Promise.coroutine(function* (
) {
30     var google = new WebResource("http://google.com");
31 
32     var hash = yield google.hash();
33     var contents = yield google.contents();
34 
35     console.log(`${hash} ${contents.substring(0, 50)}...`);
36 })();

You can find this code and a TypeScript version over at GitHub repo.

Separate the two - construction and async initializing

It's cumbersome to make a constructor perform asynchronous calls. It's best to leave constructor set up the internal state of the object at the variable level without making any calls. A dedicated initializer method can be used to make asynchronous calls and finish setting up the object state. The method can either be called by the user of the object or internally at the beginning of each public method.

Related articles

cover

There are 350 000 NPM packages. Your task: pick one.

Learn what to look for in a reliable NPM package.

Get Guide

Share article: