Any Reason Not To Always Use Async

I realise using Async is sometimes mandatory and also its good to use if we need to wait to a certain task to complete before allowing the code to move.

Is there any downside of getting into the habbit of always using Async for all function calls?

1 Like

Async will create a promise of the returned value automatically, which isn’t really what you want for synchronous code like

function sum(a,b) { return a + b}

It would be quite a bad habit to simply add async to every function and even bloat up your code as you’d have to await everything.

Something like await sum(2,3) is not really expected or intuitive if the function code itself has no asynchronous parts in it.

1 Like

Thanks Simon, nicely explained and I can now see why it would not be such a good idea to use Async in every function call.

1 Like

Another question that comes to mind in this regard is: When calling a DOM APIs that returns a promise, should “await” be used before the call?

For example:

showAlert#

▸ showAlert ( message : string): Promise‹void›

Shows an alert in Photoshop with the given message

Should that be called with

await showAlert("message");

1 Like

And what about DOM APIs that the documentation doesn’t tell us what they return. Example:

const app = require('photoshop').app

Does that need “await”?

Or are all the DOM APIs executed synchronously by default and none of them need “await” unless you plan to “.then()” them.

1 Like

It depends on what order of calls do you prefer. Sometimes it doesn’t matter and you want to do multiple calls together to get work done faster. If you want to show an alert but not to wait until user click it then you don’t need to await. But keep in mind that in many cases PS can do only one thing at the time. :smiley:

1 Like

Getters in general in PS DOM are sync. @Fionayu this is something that could be noted in documentation.

1 Like

Require is including external modules, which is always synchronous.

Await actually replaces .then, so you wouldn’t ever pair them together.

1 Like

Thanks, Simon. That makes sense.

I could still use

await require('photoshop').action.batchPlay([...

to make sure different batchPlay commands execute in the correct sequence, but then not use .then() to continue from there.

Also, based on the @Jarda comment that “Getters in general in PS DOM are sync,” are the best batchPlay options to use for getters:

{"synchronousExecution": true, "modalBehavior": "wait" }

1 Like

Yes, this time you’re including a function (batchPlay()), which of course can be async. The app from your previous example is just an object, so it doesn’t have anything to do with sync/async.

I’m not sure about the modalBehavior thing (I don’t use it in my descriptors), but yes, "synchronousExecution": true is what you want for getters that your write with batchPlay.
Usually you want to use the value you’re getting in the next few lines of code anyways, so it wouldn’t be practical to have an async getter.

I’m saying this of course assuming that getting descriptors via batchPlay usually takes a few milliseconds. If you’re getting values from a DB or via http etc. you’ll probably have some async getters.

1 Like

This is slightly more advanced, and I’m just adding it for context

While I agree with the general sentiment, “ever” (or “never”) is a strong word :stuck_out_tongue_winking_eye: . Sometimes (!), it can be useful that .then(f) again returns the return value of f(), which can, again, be a Promise, allowing us to write “pipelines,” so to speak, in an easy (and sometimes easier than encapsulated awaits) to read way :wink: .

Example:

const manipulatedJsonData = await fetchJSONString()
  .then(s => JSON.parse(s))
  .then(j => asynchronouslyManipulateJSON(j))

// instead of

const manipulatedJsonData = await asynchronouslyManipulateJSON(
  JSON.parse( await fetchJSONString() )
);

(This option/feature of promises is, at least where I see it, often overlooked and can sometimes really help to make code more readable, which is why I wanted to mention it here)

1 Like

Thanks for adding this, you’re right, I guess I shouldn’t use “never” that easily :smiley:
I didn’t have a use case like that yet, but I get the point. I’d personally split it apart into multiple variables like

const data =  JSON.parse(await fetchJSONString())
const manipuldatedData = await asynchronouslyManipulateJSON(data)

but I assume in some cases .then-chaining might look cleaner. But I’d probably add another if()-check before the JSON.parse() to be sure that the fetch function doesn’t return undefined.

Back to my initial statement, what I meant to say is that in a simple Promise case you’d either have

await promise
or
promise.then(),

as await promise.then() is redundant unless you’re returning another promise in the callback, as you’ve mentioned.

1 Like

Coming back to the original question, here’s an example that I always think makes it quite obvious (even without much technical knowledge about the “internals” of JS) why having too much async is … not optimal:

Let’s consider a simple set of two functions that can easily be run synchronously:

function incrementValue(v) {
    return v + 1;
}

function syncFunction(a, b, c) {
    return incrementValue(a) - incrementValue(b) * incrementValue(c);
}

Of course, as a developer, it would be quite easy to make this asynchronous (while it makes the code a little more complex, it’s still pretty readable, and we have to add a few asyncs and awaits):

async function incrementValue(v) {
    return v + 1;
}

async function asyncFunction(a, b, c) {
    return (await incrementValue(a)) - (await incrementValue(b)) * (await incrementValue(c));
}

The thing is that async and await are merely “syntactic sugar”, making rather complex code more human-readable. What (more or less, depending on how well the JS interpreter optimizes it) we just changed our code to, when viewed as it gets run, without the syntactic sugar, is actually:

function incrementValue(v) {
    return new Promise((resolve, reject) => {
        resolve(v + 1);
    })
}

function asyncFunction(a, b, c) {
    return new Promise((resolve, reject) => {
        incrementValue(a).then(aIncremented => {
            incrementValue(b).then(bIncremented => {
                incrementValue(c).then(cIncremented => {
                    resolve(aIncremented - bIncremented * cIncremented);
                })
            })
        })
    })
}

When we now compare this to our first synchronous example, it should be (without knowing any internals) obvious which code is easier / more performant :wink:. Therefore, async is great when needed (don’t be hesitant to use it when you actually deal with asynchronous stuff), but it also hides a lot of complexity that, in turn, make your code more complicated and less performant, meaning you shouldn’t use it if you don’t need it.

I hope this “visualization” helps a little bit :slightly_smiling_face: :

(please note that many interpreters can optimize for some of the problems mentioned in my example, but not all of them, and this, of course, is still an elementary example)

1 Like

Thanks for the additional comment @pklaschka. I had been adding .then() to some getters that I was executing asynchronously awaiting the get, and everything was working. I ran the getters asynchronously because running them synchronously ("synchronousExecution": true) breaks the promise and I couldn’t then() them then. Based on your breakdown of what’s happening when things run asynchronously, it’s probably inefficient to do anything like getting asynchronously if you don’t have to. I’m working through my code and separating out getting from using the got value, which works fine too and will supposedly make things execute more efficiently.

I guess one question I still have is how far you can push synchronous execution.

For example, in the code below, is it OK to include two batchPlay calls in one batchPlay command and still run it synchronously?

let newColor = require('photoshop').action.batchPlay([
        //Select RGB
        {"_obj": "select", "_target": [{"_ref": "channel", "_enum": "channel","_value": "RGB"}], "makeVisible": false},
        //Open Color Picker
        { _obj: "showColorPicker" }
        ], { "synchronousExecution": true, "modalBehavior": "wait" });

Or is it better to break this into two batchPlay commands like:

await require('photoshop').action.batchPlay([
        //Select RGB
        {"_obj": "select", "_target": [{"_ref": "channel", "_enum": "channel","_value": "RGB"}], "makeVisible": false},
        ], { "synchronousExecution": false, "modalBehavior": "wait" });

followed by:

let newColor = require('photoshop').action.batchPlay([
        //Open Color Picker
        { _obj: "showColorPicker" }
        ], { "synchronousExecution": true, "modalBehavior": "wait" });

I’m thinking it’s probably best to include only one batchPlay call in a synchronous batchPlay command, but that does increase the amount of code slightly compared to just chaining everything together and consuming newColor using .then(). Hope that makes sense in case anyone has any “best practices” guidelines for things like this.

Maybe a more fundamental question is whether

{ _obj: "showColorPicker" }

is even a getter. It lacks the "_obj": "get" that is common to all other getters.

To further add to the confusion around what is and isn’t a “getter” – in JS, only those fields defined with get (or defined as a getter using defineProperty) are getters. batchPlay is its own thing.

class Layer {
   get name { return this._name; } /* this is a getter */
}

const theLayerName = aLayer.name; /* accessing the getter */

batchPlay is a totally separate thing here – even though there may be “get” commands there, they aren’t related to JS getters.

I rarely ever use multiple batchPlay calls in my plugins, and it’s working perfectly fine. As described here, it may even execute way slower than stacking multiple descriptors in one batchPlay when using the new API (but that’s a different topic).

I usually put everything in one single batchPlay call. I think even if you have two async batchPlay calls following each other (without await), it might not even cause a problem in most cases. If I understood it correctly, Photoshop puts these on some kind of “execution” queue and it can’t execute multiple things parallely anyways, can it? I personally never had any race conditions or false order of execution.

I wouldn’t call that a getter. Sure, it will return the color in the end, but it’s based on user input and can be undefined, too. If the user decides to go make some coffee in the meantime, showColorPicker might return the value after a few minutes, which is the best example for async behaviour :slight_smile:

Based on the experience in this thread, it might be best to avoid:

{"synchronousExecution": true, "modalBehavior": "wait" }

as options in batchPlay getters.