0

In a TypeScript web project I am using structuredClone to deep-clone an AxiosError object from the Axios library, defined as:

export interface AxiosError<T = any> extends Error {
  //...
  isAxiosError: boolean;
  toJSON: () => object;
}

interface Error {
    name: string;
    // ...
}

My code:

function f(axiosError: AxiosError<ArrayBuffer>): void {
  const copy = window.structuredClone(axiosError);
  const a = typeof axiosError.name === "undefined";         // false
  const b = typeof axiosError.isAxiosError === "undefined"; // false
  const c = typeof copy.name === "undefined";               // false
  const d = typeof copy.isAxiosError === "undefined";       // true
}

Upon further investigation, it appears that all of the parent properties are cloned, but the child properties are not. Why? I can reproduce this in Firefox and Chrome.

Marco Eckstein
  • 4,448
  • 4
  • 37
  • 48
  • Are you able to successfully clone an instance of the `AxiosError` class? [Here's a reproducible example](https://jsfiddle.net/L2vmt56d/) which fails to clone for me (using Chrome v`109.0.5414.119`): it throws a `DOMException` instead. – jsejcksn Jan 24 '23 at 22:42
  • Yes, otherwise I could not have made the observations about the missing properties. The error `DataCloneError: Function object could not be cloned` is familiar to me, however. I think I got that when I had tried to directly deep-clone `axiosError.response`. But that was also strange because I could not find a function object inside. – Marco Eckstein Jan 24 '23 at 22:59
  • [^](https://stackoverflow.com/questions/75227478/why-would-structuredclone-ignore-properties?noredirect=1#comment132748580_75227478) Hmm... so, to be clear, what console output do you see in the example I shared and what is your browser + version? – jsejcksn Jan 24 '23 at 23:06
  • When I run your example on jsfiddle.net, the clone fails. I have Firefox 109.0 and Chrome 109.0.5414.75. – Marco Eckstein Jan 24 '23 at 23:25
  • By "parent" and "child properties" you refer to the two interfaces respectively? – Bergi Jan 24 '23 at 23:44
  • Does axios `extend` the native `Error` class? I doubt you can clone those. – Bergi Jan 24 '23 at 23:45
  • I’m guessing, it’s `window`, not “windows” – Parzh from Ukraine Jan 24 '23 at 23:49
  • 3
    @Bergi yes you can, now. It used to be impossible but sending such an Error through `postMessage` was so common that [it's been added to the specs](https://github.com/whatwg/html/issues/4268), though cloning a sub-classed Error like that would only return a basic `Error` out of it. – Kaiido Jan 25 '23 at 00:42
  • @ParzhfromUkraine Yes, that was a typo. Fixed. – Marco Eckstein Jan 25 '23 at 01:08
  • @Bergi Yes, that's what I'm referring to. And yes, it's the native error class. – Marco Eckstein Jan 25 '23 at 01:11

1 Answers1

3

If you're asking why your sub-classed AxiosError only contains the default properties of the parent Error class, that's by design.

structuredClone is meant to be used to send data across realms. The receiving realm may have no clue about your AwesomeError class, and so the object is normalized to what all realms will know about, here an Error, but the same applies to other cloneable interfaces like Set, Map etc.

So indeed, if you stucture clone an instance of a subclass of Error, all you get are the default properties of Error.

Note that there is this issue which aims to at least make .name be treated exceptionally in the cloning algo, but it still requires implementers interest (read, "it's not done yet").

Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Correct. I find this very surprising though, and I don't understand the reasoning behind it - after all, you can `structuredClone` other custom objects, and the function docs mention nothing that suggests it is not a generally applicable function. But it is what its is. – Marco Eckstein Jan 25 '23 at 02:11
  • I've created a [little demo](https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgLIE8CiUoHsrIQAekIAJgM7LZ4EDeAUMsjLrgFzIVhSgDmDAL4MGCADZwKVDDXyYSEcv0ILyVWfSYs2yALzIARK1wGtuAG7ReZFCDgBbFPoMyc+U8NESpaLG6gAkvYADmIQjuDKwCFhEWDq-siMzMZ6hsamzHaOaS5+tJnIjlJwfE6GHiIIuCDcRflyqmT8AMJiNeXcUACuCGDdUBBkbR0AFCAQAO6+GvKkzSB8owCUy6I1dfYNgTHhimCt7RNpXb39g8NHEONTM-5BoXuRiytr67W4YQB07UsABsZdAASOhbWZNQ4dL7GQTIbIQYGg7ZzRQLPgjCZfeGCP5raofb6-UYAtiIsH3XZxSGYmFwhwIkHk2gPWL7akQLH0nFrIA). – Marco Eckstein Jan 25 '23 at 02:12
  • 1
    "you can `structuredClone` other custom objects". You mean bare `Object` instances, right? If you do `class Foo { constructor() { this.own = "own" } get common(){ return "common"; } }; const instance = new Foo(); Object.defineProperty(instance, "ownget", { get(){ return Math.random(); }, enumerable: true }); const copy = structuredClone(instance)` `copy` will not be an instance of `Foo`, but a bare `Object`. It won't have access to `Foo`'s `common` getter, but it will have its `own` property set, and even its `ownget` one, though it will not be a getter anymore, but just a value. – Kaiido Jan 25 '23 at 02:27
  • 1
    The same happens with other classes that can be cloned. `Error` is one such class, that has its own cloning steps because we want that an `Error` from a realm still be an `Error` in another realm. Just like we want an `Array` in a realm to still have its `.length` to work after its been cloned. However all the properties that came from your custom subclass can't be cloned to another realm, since that subclass may not exist there. – Kaiido Jan 25 '23 at 02:30