Using ES6 promises to write async “evalScript()” calls in Photoshop
︎
Update 4/10/2018: Our CEP Getting Started resources are now on GitHub! See our repo for the latest information.
Today, I want to write about a more advanced topic for those who are already using CEP to build extensions for Adobe Creative Cloud apps. If evalScript()
is an unfamiliar term to you or you have never built an Adobe panel before, you might want to consider reading my blog post on creating your first panel in 6 easy steps first.
The CSInterface.evalScript()
method, available in CEP, enables developers to pass executable ExtendScript code to host applications, like Photoshop, Illustrator, Premiere Pro, and others. However, there is one very important aspect of evalScript()
you should know: Photoshop runs the function asynchronously, while the other Creative Cloud host apps run it synchronously.
What is an “asynchronous” function?
Simply put, it means the function does not block the rest of the code from executing. Imagine you have a function that takes 15 seconds to execute. If the function is synchronous, the rest of your code has to wait until the task completes. This will surely create a bad experience for your users! On the other hand, asynchronous functions help the code execute faster because multiple, highly computationally intensive tasks can execute in parallel.
︎
If you are interested in learning more about how JavaScript makes asynchronous functions possible, make sure to read Concurrency model and Event Loop on MDN.
How can I write asynchronous code?
The most commonly used method is callbacks. A callback is a function which is passed as an argument of the main function and invoked only after the main function completes. Let’s see how callbacks can be used in CEP.
https://gist.github.com/dkstevekwak/5ea48219fb8c72defe3acd74209e49f7#file-promise-js
Note: If you are not familiar with using template strings, I’d recommend reading Ash’s article on Using template strings in CEP.
As you can see, callbacks, by design, are nested, creating arrow- or pyramid-shaped code blocks. Now, let’s Imagine you have 10 nested callbacks or more. This block of code will soon start looking very messy and become almost unreadable.
︎
How can I make the code more readable while still taking advantage of the power of asynchronous functions?
The answer is ES6 (also known as ES2015) promises. It’s true that promises have been around for a while in libraries like Q, Bluebird, and others. But ES6 has finally adopted the same spec for its promise implementation.
Promises enable you to handle asynchronous processing in a more synchronous fashion, making the code much more readable by getting rid of the nested callback structure and letting you use normal language features like return
.
So how can I use promises when developing a panel?
Let’s start by writing a helper function called runEvalScript(script)
.
https://gist.github.com/dkstevekwak/5fe183357ebc1d4e9b177e17f30a2d22#file-runeval-js
This helper function will create and return a new Promise
instance, which represents the future result of evalScript()
. Now, let’s take a look at how we can use this to promisify the nested callbacks:
https://gist.github.com/dkstevekwak/053bab32411aaa0b94f2699e1ae17813#file-promisified-js
You might say that’s not much shorter. However, we no longer have the arrow-shaped nested callbacks and the code is much cleaner and more readable than before.
Are there any other advantages of promises?
There are more advantages! Error handling is extremely important in programming since it helps create a smooth user experience and helps you pinpoint exactly what part of your code caused the error. ES6 promises provide a super easy way to catch errors in any part of your asynchronous code.
https://gist.github.com/dkstevekwak/0c3a25c8091e736dee0b71f604a85c5d#file-errors-js
You can add .catch()
in any part of the chain to capture and handle the error appropriately.
Finally, if you have multiple functions that you want to fire off simultaneously and want to wait for all of them to finish, simply use Promise.all()
.
https://gist.github.com/dkstevekwak/87710e39d15db2f8114e7e235f510d9b#file-all-js
Imagine writing the same code using callbacks. It would be extremely difficult and messy!
While promises and callbacks are not fundamentally different and serve the same purpose, promises will help you better organize your code with more flexibility. Are you convinced yet? Please comment below. We’d love to hear from you!
And go check out CEP on Adobe I/O to get started building your own panel.
For more stories like this, subscribe to our Creative Cloud Developer Newsletter.