async programming
getters and setters
asynchronous methods
JavaScript
software development

How to call an async method from a getter or setter?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

JavaScript getters and setters look like properties, but they are still synchronous language features. That means you cannot make a setter awaitable through assignment syntax, and while a getter can return a Promise, doing so usually makes the API harder to understand than using explicit async methods.

Why Getters and Setters Are a Bad Async Boundary

Property access suggests cheap, immediate work. Async code suggests delayed work, possible failure, and await. Those two mental models do not fit together cleanly.

For example, this getter is legal JavaScript because it returns a Promise, but it is easy to misuse:

javascript
1class UserStore {
2  get profile() {
3    return fetch("https://example.com/api/profile").then((res) => res.json());
4  }
5}
6
7async function main() {
8  const store = new UserStore();
9  const profile = await store.profile;
10  console.log(profile);
11}
12
13main();

This works, but store.profile looks like a value when it is really an asynchronous operation with network cost.

Prefer Explicit Async Methods

The clearer pattern is to expose async work as a method with a verb in its name.

javascript
1class UserStore {
2  async loadProfile() {
3    const response = await fetch("https://example.com/api/profile");
4    return response.json();
5  }
6}
7
8async function main() {
9  const store = new UserStore();
10  const profile = await store.loadProfile();
11  console.log(profile);
12}
13
14main();

Now the caller can see immediately that the operation is asynchronous and may need await, error handling, and retries.

Use a Getter for Cached State, Not for Fetching

One reasonable compromise is to keep the getter synchronous and have it expose cached state, while a separate async method refreshes that state.

javascript
1class UserStore {
2  #profile = null;
3
4  get profile() {
5    return this.#profile;
6  }
7
8  async refreshProfile() {
9    const response = await fetch("https://example.com/api/profile");
10    this.#profile = await response.json();
11  }
12}
13
14async function main() {
15  const store = new UserStore();
16  console.log(store.profile);
17
18  await store.refreshProfile();
19  console.log(store.profile);
20}
21
22main();

This preserves the normal meaning of a getter. Reading the property is cheap, while the network call is explicit.

Setters Are Even More Awkward

A setter cannot be awaited through property assignment syntax. You can start asynchronous work inside a setter, but the caller cannot write await obj.value = 3 and expect meaningful sequencing.

javascript
1class SettingsStore {
2  #theme = "light";
3
4  get theme() {
5    return this.#theme;
6  }
7
8  set theme(value) {
9    this.#theme = value;
10    void this.saveTheme(value);
11  }
12
13  async saveTheme(value) {
14    await Promise.resolve();
15    console.log("Saved theme:", value);
16  }
17}

This is valid, but it hides asynchronous side effects behind assignment. That usually makes debugging harder.

Replace Async Setters With Async Methods

If setting a value requires async work, use an async method instead of a setter.

javascript
1class SettingsStore {
2  #theme = "light";
3
4  get theme() {
5    return this.#theme;
6  }
7
8  async setTheme(value) {
9    await Promise.resolve();
10    this.#theme = value;
11    console.log("Saved theme:", value);
12  }
13}
14
15async function main() {
16  const store = new SettingsStore();
17  await store.setTheme("dark");
18  console.log(store.theme);
19}
20
21main();

This makes sequencing explicit and gives the caller a natural place to handle errors.

Good API Design Rule

A simple rule works well here:

  • getters should expose already-available state
  • setters should perform immediate synchronous state updates
  • async work should live in clearly named methods

That rule makes your objects easier to read and easier to test.

Common Pitfalls

The biggest pitfall is hiding network or disk I/O behind a getter. Code that looks like ordinary property access can suddenly block on await, fail with transport errors, or trigger duplicate fetches.

Another issue is putting async work inside a setter and assuming the caller can coordinate with it. Assignment syntax does not provide a clean await point for that design.

Developers also create getters that fetch fresh data every time they are read. That can turn harmless-looking logging, rendering, or debugging code into repeated remote calls.

Finally, avoid APIs where some properties are plain values and others are promises without a naming convention. That inconsistency creates avoidable confusion.

Summary

  • JavaScript getters and setters are synchronous property syntax, even when they interact with promises.
  • A getter can return a Promise, but that usually makes the API less clear.
  • A setter cannot be meaningfully awaited through assignment syntax.
  • Prefer async methods such as loadProfile() or setTheme() for asynchronous work.
  • Use getters for cached or already-available state, not hidden I/O.

Course illustration
Course illustration

All Rights Reserved.