Archive

2nd of July, 2026

You might have recently learned that Nicholas is leaving Anaconda Inc. and, consequently, he will no longer work on the PyScript project.

He wrote more in our Discord channel, and the TL;DR for me is: thank you Nicholas, you’ll be missed, yet I’m sure you’ll do great things and we’ll meet again!.

Meanwhile …

  • we released PyScript 2026.6.2 as a “handover exercise” after Nicholas wrote down all the steps to coordinate the code release, related docs, CDN links, and everything else; it went so well that …
  • I released PyScript 2026.7.1on my own” (he approved my MRs though)—not because I enjoy releases for the sake of it, but simply because Pyodide 314.0.2 went out right after we released our latest version the day before
  • I keep updating @webreflection/utils with all common patterns we use and need in PyScript and related projects … more to come, keep reading!
  • some discussion around named workers in Discord might be worth following because there were surely concerns around what’s possible and what isn’t straightforward … in PyScript we tried to make complex things simple but behind the scenes is one of the most complex Web architectures one might use and we could track every single possible bit of the code, bloating our core with debugging details and users’ mistakes or we can keep it fast and simple, but we need to help more whenever it’s possible, either with docs, examples, or both … we’re constantly trying to improve but our time, and right now our capacity, is extremely limited so bear with us …

That’s it; aside from the fact I’ve improved other libraries to simplify ad-hoc challenges, there’s not much else this time to talk about around PyScript.

About JS Accessors

Internally, we use AI to maintain the highest code-quality standards and avoid early or rookie mistakes during reviews. One particular review was flagged as “excellent … but!” and I had to explain back—not to the AI, but to whoever was interested in those AI comments around that “but“—why I made that decision, that code choice, why I used that style. This time it was about asynchronous accessors, a topic I barely remember yet one that, if more people knew about, is worth another take …

What is an accessor?

If you read carefully MDN Object.defineProperty documentation around descriptors you’ll notice that there are two main categories:

  • data descriptor, whose peculiarity is having a value and, optionally, a writable field
  • accessor descriptor, which cannot have either of the previous fields if it contains a get and/or a set field
  • the only fields these two kinds of descriptors have in common are configurable and enumerable:
    • configurable allows other Object.defineProperty operations, no matter the kind of descriptor, no matter if writable was false, either
    • enumerable exposes the property in for/in loops, Object.entries, Object.ownKeys, and anything else that considers enumerable properties, which is false by default (and thankfully so!)

A data descriptor that is not writable basically acts like an accessor that has only a get but not a set, but the latter kind allows developers to hide implementation details and “do more” when a property is reached.

If you think that’s not a great pattern, imagine that almost the entirety of the DOM is based on accessors defined either at the prototype level or directly attached to the reference, and while the DOM might not be a great example to showcase, a lot of native JS APIs (mostly browser based but also server ones) use accessors for various reasons, hinting these are a pattern one might choose when it’s convenient.

Synchronous Accessors

Theoretically one could define a get or set accessor that is either async or returns a Promise, but that’s usually not the case.

In PyScript, as an example, we have proxies that simulate a get accessor that blocks the worker world via Atomics, waits for main to resolve the request into a SharedArrayBuffer, and frees the Atomics operation after writing into such buffer.

And what do you write as a user?

from pyscript import document

print(document.title)

That’s it: you don’t care about the dance behind, you don’t care about implementation details, you just use the code in a regular way … now, imagine we do something like this there (we don’t—this is just simplified meta-code):

Object.defineProperty(document, 'title', {
  get() {
    // ask to resolve at the Worker/MessageChannel owner level
    channel.postMessage({ context: document, prop: 'title' });
    // wait on a SharedArrayBuffer Int32Array view
    Atomics.wait(sbView, 0, 0);
    // return the result back - length of the buffer/answer
    // stored at index 0, if undefined that's `0` itself
    if (sbView[0] !== 0) {
      // if not, we parse the relevant answer as binary
      return ourLogic.parse(sbView.subarray(1, sbView[0]));
    }
    // it's undefined otherwise
  }
});

The same thing we could do with a set(value) for anything that allows being set on the main thread and yet we can do tons of operations as long as these are synchronous.

The power of accessors is enormous but it falls short when SharedArrayBuffer cannot be used or when a single unique operation is asynchronous so that atomicity of the operation cannot be granted anymore because it cannot “backfire” if something goes wrong.

Accessors VS explicit methods

The long and short of it about accessors is that these confine details per name or field:

// get
ref.thing;

// set
ref.thing = value;

There are programming languages though where the following syntax is preferred instead:

// get
ref.getThing();

// set
ref.setThing(value);

JS has the “tiny caveat” that methods are not self-bound and especially methods that deal with internal properties—this is a huge bummer, a memory consumption trap (bloated due to bound methods all over), or … you name it.

With accessors you are almost certain that the context of that field will be the ref itself (as in the example) and nothing else.

Accessors here guarantee, with ease and in a natural/elegant way, you don’t have to think about using the right method, invoking it through the right context and so on: ref.thing does all that, ref.thing = value explicitly sets such value if possible, it throws naturally otherwise if not defined.

The method counter example via ref.setThing(value) won’t tell you that thing had a get method but not a set one, it will fail because undefined is not a function which is one of the most known JS errors printed in history … and that’s a bummer both for you and users of your library.

A missing set within an accessor will clearly state, in strict mode, which is the default with modules and modern syntax:

Cannot set property thing of #<Object> which has only a getter

The thing field is there, the error is clear, less debugging, less code for the user, less syntax to care about, everyone happier?

Are you convinced yet? Well, I’ve been using accessors or simulating accessors via proxies for years now, and I can assure you I’ve been sold for a long time.

Asynchronous Accessors

Now here the catch: there is no way to port the beauty of data accessors into asynchronous methods and here is why:

const bummer = Object.defineProperty({ _: 42 }, 'thing', {
  // this could be either sync or async
  get() {
    return this._;
  },
  async set(value) {
    this._ = await new Promise($ => setTimeout($, 0, value));
  }
});

// this is fine (even if not needed)
console.log('get', await bummer.thing);
// get 42

// this fails though + no way to await
await bummer.thing = 43;

// because it cannot be sync'd
console.log('get after set', await bummer.thing);
// get after set 42 ⚠️ !!!!!!!!!

In a few words, an asynchronous setter cannot be awaited, if not internally, but an asynchronous getter can always be awaited.

await bummer.thing = 43 would throw an error due to invalid syntax while await (bummer.thing = 43) would await the value 43 which is not even a Promise, hence it will resolve immediately after so … what can we do?

jQuery is dead, long live jQuery !

In an era where asynchronous patterns were little known or used, also an era where accessors were not always possible to define, the most popular library of many consecutive years, still popular in some corner of this world in 2026, proposed a very simple contract/pattern:

// retrieve the text content of the body
$('body').text();

// set the text content of the body
$('body').text(value);

That pattern is effectively an accessor expressed through a method:

  • no arguments means intent in reading the value
  • one argument (and usually one only) means intent in setting the value

How simple is that? 🤯

When it comes to mimicking asynchronous accessors I find indeed the single callback used to read or write, no strings attached, a function that always returns a Promise, no matter how the get/set were defined, would be semantic, easy to reason about and, thanks to JSDoc or TypeScript in general, guarded in terms of intents, and this is what today @webreflection/async-accessor is all about:

import asyncAccessor from '@webreflection/utils/async-accessor';

const thing = asyncAccessor({
  /** @type {number} */
  _: 42,

  /** @type {() => number} */
  get() {
    return this._;
  },

  /** @type {async (value:number) => void} */
  async set(value) {
    this._ = await new Promise($ => setTimeout($, 0, value));
  }
});

// this is fine (even if not needed)
console.log('get', await thing());
// get 42

// this works + we can await
await thing(43);

// because it can be sync'd
console.log('get after set', await thing());
// get after set 43 🥳

And that’s it:

  • the utility type is inferred by the signature you define in both get and set
  • by contract and TypeScript definition, you either don’t pass a value or you do
    • when you don’t, you receive a Promise<type> in return
    • when you do, you receive a Promise<void> in return
  • no matter if definitions are sync or async, the returning value is guaranteed
  • the name used to define that async accessor still keeps logic and intents confined within a single name; it’s up to you to define a set that throws an error if you want that to be read-only—semantics are preserved but we need to await both
  • this pattern has been working for many years and made developers using jQuery extremely happy about that library; any argument around it might feel unnecessary … it works, it’s simple, happy coding?

Again, like many other micro utilities in that package, feel free to use or ignore this one but I hope you enjoyed reading about the reason I sometimes like this approach and use it, when it’s convenient, plus what the benefits are 👋