Skip to content

Instantly share code, notes, and snippets.

@goloroden
Last active June 22, 2023 02:10
Show Gist options
  • Save goloroden/c976971e5f42c859f64be3ad7fc6f4ed to your computer and use it in GitHub Desktop.
Save goloroden/c976971e5f42c859f64be3ad7fc6f4ed to your computer and use it in GitHub Desktop.
Async constructors for JavaScript
// In JavaScript, constructors can only be synchronous right now. This makes sense
// from the point of view that a constructor is a function that returns a newly
// initialized object.
//
// On the other hand, it would sometimes be very handy, if one could have async
// constructors, e.g. when a class represents a database connection, and it is
// desired to establish the connection when you create a new instance.
//
// I know that there have been discussions on this on StackOverflow & co., but
// the so-far mentioned counter arguments (such as: doesn't work as expected
// with new) didn't convince me. Actually, it works as expected (see below),
// as long as you use await – but this is true for any async function ;-)
//
// Could this be an idea for TC39?
'use strict';
const { promisify } = require('util');
const sleep = promisify(setTimeout);
class Example {
// This is what I would like to have.
// async constructor () {
// await sleep(1000);
// this.value = 23;
// }
// This is how it could be implemented.
constructor () {
return new Promise(async (resolve, reject) => {
try {
await sleep(1000);
this.value = 23;
} catch (ex) {
return reject(ex);
}
resolve(this);
});
}
}
(async () => {
// It works as expected, as long as you use the await keyword.
const example = await new Example();
console.log(example instanceof Example);
console.log(example.value);
})();
@mannharleen
Copy link

Here's something I've been playing with the past couple days

function Post(db, post) {
  if (!new.target) {
    return new Post(db, post);
  }
  this.db = db;
  this.post = post;
  //
  // create new post
  //
  return new Promise(async (resolve, reject) => {
    try {
      //
      // if we have an id then the post is already in the
      // database and we have already loaded it!
      //
      if (!post.id) {
        let id = await this._insert(post);
        this.post.id = id[0];
      }
      resolve(this);
    } catch (err) {
      reject(err);
    }
  });
}
  let post = await Posts.create({
    title: 'title',
    body: 'body',
    author_id: author.id
  });
Posts.create = async function(params) {
  return Post(state.db, params);
}

good one!

@bakasura980
Copy link

bakasura980 commented Jan 6, 2022

There is one more way of doing an async constructor - Proxy

Typescript:

class A {

    private data: any
    private info: any

    public constructor (data: any, info: string) {
        this.data = data
        this.info = info
    }

}

interface InterfaceA {
    new(info: string): A;
}

class AProxy {

    public static init (): InterfaceA {
        const proxyHandler = {
            construct: async function (args: any) {
                // Some async operation
                const data = await retrieve()
                return new A(data, args)
            }
        }

        return new Proxy(A, proxyHandler)
    }

}

export default AProxy.init()

import A from './a.ts'

(async () => {
    const a = await new A('Some iNFO')
})()

Javascript:

class A {
    constructor (data, info) {
        this.data = data
        this.info = info
    }
}

class AProxy {

    static init () {
        const proxyHandler = {
            construct: async function (args) {
                // Some async operation
                const data = await retrieve()
                return new A(data, args)
            }
        }

        return new Proxy(A, proxyHandler)
    }

}

module.exports = AProxy.init()

But to be honest, if you need an async constructor, it is always better to use the build pattern as it is more readable and understandable

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment