Today’s tip focuses on the Asynchronous nature of CSInterface.evalScript()
calls, and on ways to make it work in a synchronous fashion.
Synchronous vs. Asynchronous
Quoting StackOverflow:
When you execute something synchronously, you wait for it to finish before moving on to another task. When you execute something asynchronously, you can move on to another task before it finishes.
Or visually:
Back in Flash days evalScript
was synchronous, now in HTML land is asynchronous:
Async example
Remember that evalScript function takes two params:
- The ExtendScript code to run.
- A callback function, which in turn takes as a param the returned value from the ExtendScript call.
The default behavior is as follows.
In the above example the app.documents.length
statement is executed and directly returned - the number of currently opened documents in Photoshop is passed to the callback, which assigns it to the numberOfDocuments
variable. The alert says “undefined” because of the asynchronous nature of evalScript: line #4 executes, then the interpreter goes ahead to line #8 not waiting for the ExtendScript to return (in this example, a single line of code, but in real world a long chain of functions that trigger usually time consuming Photoshop operations) so when alert(numberOfDocuments)
is called, numberOfDocuments
is still undefined. So to speak, it is like asking your partner to do the shopping then, as soon as (s)he goes out, open the fridge and say “rats, it’s empty!”
Sync workarounds
There are few different ways to mimic a synchronous behavior
1. Set a Timeout
The easiest way is to just wait for your partner to get back from the store:
Although, it’s not ideal. You might not know in advance how long to wait - it depends of the kind of ExtendScript task you’re executing.
2. Nest code in Callbacks
If the alert is moved within the callback it works properly:
Of course this can lead to the so-called callback hell – nesting evalScript calls one inside another, like:
Yet if you’ve not fancy needs, it works.
3. Event driven callbacks
This method is supported only from CEP 5.2 onwards. You can implement custom Events via PlugPlugExternalObject, that are dispatched by ExtendScript and listened from JS. I’ve put the ExtendScript code in its own JSX file:
Then in the JS you set up a listener, which is the one who fires the alert();
Mind you, as far as other devs such as Kris Coppieters have supposed, “even if you manage to make multiple JSX calls from different parts of your JS, probably you can’t run multiple ExtendScripts concurrently”.
Acknowledgements
I’d like to thank Zihong Chen and Ten from Adobe and the others who’ve helped shedding some light upon this topic in the forums.
The Photoshop HTML Panels Development Course
If you’re reading here, you might be interested in my Photoshop Panels development – so let me inform you that I’ve authored a full course:
- 300 pages PDF
- 3 hours of HD screencasts
- 28 custom Panels with commented code
Check it out! Please help me spread the news – I’ll be able to keep producing more exclusive content on this blog. Thank you!