Async In Constructor
ID |
javascript.async_in_constructor |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
JavaScript |
Tags |
async, classes, reliability |
Description
Reports asynchronous work started inside a constructor: a .then() / .catch() / .finally() continuation, or a call to a function the file declares async — a resolved async declaration, or an async method of the same class reached through this.
Only work started by the constructor itself is reported. A call nested in any function the constructor merely defines — an event handler assigned to a field, a callback passed on to be run later — belongs to whoever calls that function, so the walk stops at the first enclosing function rather than treating arrows as transparent.
new Promise(…) is not reported: constructing a promise is not starting work, and a promise stored in a field is one of the shapes this rule’s remediation produces.
An await in a constructor is not part of the check — await is legal only inside an async function and at the top level of a module, so such a file is a SyntaxError and never reaches a scan.
Overlap worth knowing: javascript.promise_result_ignored reports a discarded promise anywhere, constructors included. This rule fires whether or not the promise is kept, because in a constructor keeping it does not make the object ready.
Rationale
// Bad
class Session {
constructor(url) {
fetch(url).then(user => { this.user = user; });
}
}
const session = new Session(url);
session.user.name; // undefined — the fetch has not come back yet
-
A constructor returns the instance, never a promise. There is no value a caller can wait on.
-
The object is handed out complete-looking and unfinished, so every consumer has to guess when it becomes usable — and the guess is usually "now".
-
There is no place a rejection can go. A failure surfaces as an unhandled rejection with the construction site nowhere in the stack.
Remediation
Move the work out of construction, so the caller gets something to wait on and somewhere to put the catch.
// Good — a static async factory awaits, then constructs
class Session {
static async open(url) {
const user = await fetch(url).then(response => response.json());
return new Session(user);
}
constructor(user) {
this.user = user;
}
}
const session = await Session.open(url);
// Good — an explicit init the caller awaits
class Session {
constructor(url) {
this.url = url;
}
async init() {
this.user = await load(this.url);
return this;
}
}