Why Does this code work, but this doesn't?

It would seem that activeDocument.layers simply doesn’t function properly, and I can’t figure out why.

Code That Works

function WorkingNames() {
  const app = require("photoshop").app;
  const allLayers = app.activeDocument.layers;
  const allLayerNames = allLayers.map(layer => layer.name);
  const sortedNames = allLayerNames.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
  for (let bla in sortedNames){
    console.warn(sortedNames[bla])
  }
}

Code That DOESNT Work

function BrokenNames() {
  const app = require("photoshop").app;
  const allLayers = app.activeDocument.layers;
  //const allLayerNames = allLayers.map(layer => layer.name);
  //const sortedNames = allLayerNames.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
  for (let bla in allLayers){
    console.warn(bla.name)
  }
}

When you inspect the result of allLayers in the debugger for both forms of the function after that line executes, it is an empty array in both cases, yet somehow using the magic allLayers.map(layer => layer.name)
causes allLayers to be populated, and we get the expected output.

What am I doing wrong? Is this a rookie mistake on my part, or a very large bug?

app.activeDocument.layers is a proxy / not an actual array.
for-in does not work with proxies (source)

use for (let bla of allLayers){ the key is using of instead of in.

it seems like array high order functions (map and sort) return an actual (new) array that works with for-in

Wow, so much to learn about Javascript! I have never heard of proxies or traps before. They appear to be magic to the untrained eye. Its especially confusing that proxies don’t reveal their data while on a breakpoint and inspecting them after assignment, instead appearing as empty versions of whatever they are “wrapping”. I suppose that might be from some kind of custom trap for an essential function?

I’ll sum up my learning here and provide links to the articles that helped me understand proxy and trap.

A proxy creates an instance (or is it a pointer?) of an object with the primary intention of changing how it’s essential functions work (e.g. enumerate, which is used during for in , and getters and setters, etc.).

And a trap is the actual new function that replaces each essential function. Hilariously, they work well as real-life traps, too.

A handler is the object that contains the traps (which are just functions). When you define a new proxy, you pass in the handler as well, thus creating a trap-riddled fake object.

Simple and clear explanation on both Proxy and Trap

Some more indepth info on them:

2 Likes

I am experimenting with proxies and I think there might be a better way how to implement them in DOM.

for in could possibly work:
image

And proxies could possibly show its content:

I will make a proposal (PS-104663) for change in DOM.

4 Likes

thanks @Jarda , that’d be great for everyone!

until then you can use:
Array.from(app.activeDocument.layers)
which return a normal array of layers

or the following function I created to get ALL LAYERS regardless of “depth”
it accepts one argument : the root of layers
it could be doc’s layers or a group of layers

function flatLayers(lys) {
   return lys.flatMap((v) => v.layers ? [v].concat(flatLayers(v.layers)) : v)
}

and use it like so:
flatLayers(app.activeDocument.layers)

2 Likes