Adobe UXP: Things you need to know! #5 Sync vs. Async code in Photoshop DOM Scripting

In this episode I’m discussing asynchronous code in the Photoshop DOM and how the new JavaScript Scripting engine differs from ExtendScript.

If you find this content useful, please consider supporting me – 2020 is a hell of a year. I don’t have a Patreon page, but I’ve got two fairly cheap plugins on the Photoshop Marketplace, ALCE (Advanced Local Contrast Enhancer) and Double USM (on sharpening). If you happen to buy them, please leave a positive rating/review, it would greatly help. Or, you can

If you cannot, or don’t want to, that’s OK anyway.
Stay safe and thanks! 🙏🏻

The whole series so far

Transcription

The transcripted video looks weird, but I’m told it helps to have it anyway because it’s easier to translate to other languages. Apologies for the lack of punctuation and the sloppy syntax.


Hey I’m Davide and this is Adobe UXP things you need to know today I’m talking about asynchronous code in Photoshop UXP scripting. Let’s say that you have some old ExtendScript code that you want to port to UXP, something very very simple like duplicating the layer and then renaming it. In ExtendScript you would do something like app.activeDocument and then activeLayer just to keep it super simple then you want to duplicate that right and probably store that into a variable var dup is equal to this so that you can reference it, dot name and then assign that to a different string like I don’t know UXP. OK so this is what you would do in ExtendScript, let’s see how this works in UXP code: it’s going to be just a little bit different but this involves the understanding of asynchronous versus synchronous code so let’s get to it. Let me create a new UXP plugin so let’s switch to the UXP Developer Tool I want to create a plugin let’s call this test the id is whatever plugin version 1.0.0 host application Photoshop am I repeating this just for you to become more and more familiar with the process 22.0.0 and again if you don’t remember that there is a video that you can check out, and the template is the easiest one the ps-starter I want to put that into the samples and let me create a folder that I’m going to call test okay so selecting that. Plugin being created I need to do three things: I have to load it, and I have to watch it, and I also want to debug it, okay so now the UXP DevTool can go away .I want to keep this on the top right corner I need to add this test folder in Atom just keep this on the left the plugin is here let me stick it to the side as well and also I have to create a dummy document in Photoshop for the sake of experimentation with a new layer that’s okay and I want to add something in it like just a circle with a nice color and let’s center this. This is a little bit of a trick if you have to center something you select all: Command+A and then with the move tool you can align it based on the entire document. OK so that’s it let me also clean everything in the index.html I don’t need no style, so this goes away, the body also everything away, I just need a button so let’s add a spectrum web component sp-button and this is going to say “run” and have an id of “test” so that let me save this. This is something related to the JavaScript file which I’m going to delete anyway so at this point I have to reference that button via id so document.getElementById the id was test I think so at this point I have to add an event listener so add event listener for the click event and when this clicks I want to have a callback function that for the time being just console.log something just to see if this works. console.log run okay so let me save this, click run and check the console. It says run, the error was pre-existing so let me get rid of that again: run run run run run. Okay cool so this is the starting point. Now let me copy the ExtendScript code as a reference the index.html we don’t need anymore let me stick it on the top here as a comment, all right, so we don’t have in UXP any app already available in the global space so we need to get that via require Photoshop so const I want app and this is going to be require Photoshop and then the app. Okay so this is one thing I suppose that I’m gonna have an active document in UXP as well but let’s check this out in the console. So app should be available here already app? is it? no app is not defined let me save this let me try again. Yes now I have it app dot does it have anything like an active document? it does, so here, this is the document you have all the information also the layers you see? All right so: the active document. Does the active document have a activeLayer property? Let’s check activeLayers plural so this is a different way that UXP goes about active layers compared to ExtendScript. In ExtendScript you had activeLayer singular you now have activeLayers plural and it is an array even if you have just one of it. It makes sense to have an array because you can have more than one layer selected with shift click in the layers palette but anyways. You can have this active layers and if you want just the first one in this case it’s just one so that’s not a big of a deal you select the one with index zero right? So this is the layer let’s check that the name is the right one so it’s layer one okay and now we can set the name for instance of it, name to a different string UXP is fine so let’s check if this works. It does, it has renamed the layer and probably that layer also has a duplicate function. So here it is we’re not reading the documentation we’re just experimenting in the console which is more fun so duplicate this duplicates the layers so everything seems to be okay. So we now have all the elements to write our code don’t we? app is here now let me have a constant also for the active document so let’s call this doc is equal to app.activeDocument and I also want to have the active layer so const lay is equal to doc.activeLayers plural and then just the first one and let me just console.log it here to see if everything works. Click run and here is the layer okay. Let’s do this duplication and renaming business. Let’s get rid of the console log I don’t need it anymore let’s write let dup is equal to lay.duplicate all right and now I can rename dup and dup.name is equal to DUP! All right? so let me get rid of that layer here we don’t need it let’s save and this goes on the corner I don’t need the documents tree also let me clear the console. Are we ready? Three, two, one run! And something has happened but not really what we expected right? So this has been duplicated but has not been renamed and you might wonder why. So let’s check in the sources and let’s stick a breaking point right here and try again so run, this has been paused on that break point we can just go through that clicking that button and you see that the layer has been, in fact, duplicated but this dup is now something called a “promise” should be a layer shouldn’t it? And now if we step over one more time we see that nothing really happens not even an error. So this is a little bit puzzling let me get rid of the break point and let me try to explain what is this all about. Now in UXP as opposed to ExtendScript there are functions that run in an asynchronous fashion that is to say that it takes some time to do some operations such as duplicating a layer because Photoshop has to take care of all the pixels and store them in memory and whatnot this might take a significant amount of time. So instead of waiting for that process to complete the JavaScript engine just goes on immediately to the next line: so it’s duplicating this and performing that duplication but the JavaScript engine just switches to the next line this dup.name and tries to run it. So it fails because since dup is not really a layer yet is not possible to rename it, okay? so the question is now what is dup? dup is a promise and a promise is a proxy for the value that dup is eventually going to be assigned. And only then when the promise is resolved dup can be renamed let me try to address this synchronous versus asynchronous thing in a more visual way. Think about synchronous code so ExtendScript so this is sync as a series of functions that happen one after the other you have the first function that is here and then the second one that happens only when the first one is completed that goes right on and then you have the third one that goes on just just when the second one is completed so it’s one two and three. They run in a synchronous fashion. Instead in a synchronous code so a sync what happens is that you have the first function that is here and this is the first one that starts to run but immediately not waiting for it to be completed the JavaScript engine goes on with the second one it just takes few milliseconds here just to run the first one before starting the second one; and then after immediately after you have the third one which is here okay so you have again a few milliseconds between the second and the third one and this is the third okay? So even if the starting order is one two three the finished line is not necessarily one two three, it really depends on how long it takes to perform the first the second and the third operation. So for instance if the first one is really intensive can take more time and then the second one might be shorter and the third one something I don’t know in the middle like that so the finishing line in this case might be something like two three one why not? And getting back to our duplicate example you might think that the duplication is started and it has still to complete and you are already trying to rename it so this happens not on the actual layer but in something that is just a proxy for that layer, hence it fails. Okay I hope this makes sense. So the next question is how we deal with asynchronous functions: there are at least a couple of different ways. First one is with then functions so every function that is asynchronous that returns a promise is then-able so you can write something like then which is a function and this then accepts in turn a callback function that I’m going to write with the fat arrow syntax and the argument of that function is the returned value of the resolved promise so in other words: you have this duplicate here and at some point this duplication happens and then a layer is returned. The proper way to say it is that when the promise is “resolved” then you have the “returned value” okay? So that return value which is a layer by the way is passed to this callback here okay so at this point you can write something lay here or let’s say layer and then you can rename it right, so layer dot name is equal to dup here okay so this line goes inside of the callback function. So let me save this and let’s check that we don’t have any pause debugger or something also let me get rid of those layers here run and here it goes so dup is here you see that it has duplicated the layer and renamed it right? The problem with then-able functions is that you might think that this dup now is a layer you have been assigning that to this duplication and then then function but the problem is that then returns a promise so even if you add return layer here. Let’s try to console.log the dup: so dup here and there, so let me save this and switch back to the console now let me try to run you say that dup is a promise, which by the way is also pending. Even if you try to assign to dup the layer inside the callback that is not really going to work so let’s say let’s dup is equal to a null for the moment and then you go with lay duplicate and instead of returning layer which is not useful now you say dup is equal to layer okay so let’s see if this works let’s save that and run you see that dup is still null so it’s kind of hard to work with that if you need to reference the resolve value of a function unless you keep then-ing functions which is by the way possible if you return them so if you return the layer you can now then again and do whatever you want in here with that layer so l and then I don’t know if you need to perform some further steps you can inside the then callback. Another way that in my opinion is way simpler is to use async and a await these two keywords are going to transform the code into synchronous code. okay let’s try them I want to say let dup is equal to await for lay duplicate all right and this await here means let’s wait for the duplication to be completed so for the promise to be resolved and then assign to dup the result. So if we try this let’s save first you see that there is an uncaught syntax error await is only valid in any sync function so we need to specify, to explicitly tell to the JavaScript engine that the function that contains this await is asynchronous so we need to add the async keyword to the function. And can you spot the function here? it is this anonymous function here, so in there you’re going to write async okay so let’s save again let’s get rid of that message and run and you see that now the dup is finally a layer. You have a lot of copies here so let me get rid of everything and try again: run, nothing happens… let me save once more. Run and you see it here we still have to rename that so at this point I’m pretty sure you can write something like dup dot name is equal to UXP well not UXP, DUP okay so let’s try this save and run and in fact you have dup here okay? So this appears to work remember that you have to use await only in the context of an async function now let me try something a little bit different say that you don’t want to duplicate the layer just once but a number of times so you want to use something like a for loop so for let i equal not 0 but let’s say 1, i less than 5 and i plus plus; standard for loop and in here you want to do this dup await the duplicate right so let’s save this delete a bunch of layers that we don’t need and click run again there is a breakpoint going on here I don’t want it let’s play everything and also let’s get rid of that which is going to be better okay so let’s save again click run and you have all the copies here. I don’t want to have just dups so let me interpolate this with the backticks so I want to have dup and then underscore with the index right i. Okay so let’s save get rid of the duplicate run it again. I don’t know why I need to save these a couple of times… run and now you have it so dup 1 dup 2 dup 3 and dup 4. So let me rewind that to show you what exactly happens which is what you expect so a duplication and the rename step. So I duplicate rename duplicate rename duplicate rename and duplicate rename okay? Cool. So let me get rid of that so let’s say that you don’t want to have a for loop but you are a fan of functional JavaScript you want to use something like map on an array of numbers which you can do by the way one two three four you can map and map takes a callback that is executed on all those elements so you have the element here and then you can just say this let dup equal to await duplicate and dup name is equal to this the only thing different is that instead of I you have the element el okay so let’s save couple of times I have an error here: await is only valid in the sync functions and again this is something that I wish I had one Euro for all the times that I got that wrong you have to put async here right so let’s save again and let’s get rid of the for loop that we don’t need anymore so I’m going to also clean the console and click run once more and apparently everything works fine dup one two three and four but let me rewind with command-z the history and see what has in fact happened. So if we play it slowly we have a duplication and a duplication and a duplication and a duplication and then a rename rename rename rename which is not really what we want do we we want a duplication and a rename. So duo plus rename a number of times instead of dup dup dup dup and rename rename rename rename. So in this case it makes really no difference but it might be that with more complex routines this kind of behavior conflicts with what you really want to perform and you got some errors that you’re not able to debug because apparently everything works fine. So the caveat here is that map and I think also forEach all those functions that run on arrays with async and await: mmmh, probably going to lead to problems so try to use for loops as much as you can in these cases. Okay, so to recap everything that we have covered so far: you have to be aware that as, opposed to ExtendScript, in UXP you have a number of functions that return promises so either you use then callbacks or you use await and async. And if you ask me what are those functions well you can find them in the documentation in the Photoshop API section you can pick modules for instance and then I don’t know what document here it is you have active layers and you see that it returns a layer Array here so this is not a promise and all those are getters and getters usually are synchronous. In this case for instance the close method returns a promise let’s check the layers so get bounce returns bounds so no promises but probably in the methods for instance nudge I think returns a promise? yes here so you’re free to check all the methods that you need in all the Photoshop API modules and check whether you need to use async await you see async here so whenever you see async you know that you have to await for stuff. Okay I think this is it for this time I hope you have learned something new if this is the case please consider supporting this channel and me which is always really appreciated. I want to thank all the people who already had contributed there is a donation link in the youtube description of this video and on my website I also have ALCE and DoubleUSM in the Photoshop marketplace; if you happen to buy them a positive rating or review are always helpful. Thank you very much and see you in the next one. Bye!