Last active
June 22, 2023 02:10
-
-
Save goloroden/c976971e5f42c859f64be3ad7fc6f4ed to your computer and use it in GitHub Desktop.
Async constructors for JavaScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
})(); |
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
good one!