Continue failed batchPlay chain

Is there a way to continue set of batchPlay descriptors after one of them fails?

For example, I could have a very long chain of descriptors and somewhere in the middle I need to delete a layer/channel that might be missing. This delete descriptor can fail and it will break the exection.
How can I continue even after it fails?

1 Like

try/catch won’t work with batchplay. Try .then(e)

Here is my batchPlay wrapper at the moment. It doesn’t continue descriptors after failure, it seems

export const batchPlay = async (
  descriptors,
  options = { debug: false, history: undefined }
) => {
  const { debug, history } = options
  var historyState = undefined
  if (history) {
    historyState = {
      name: history,
      target: {
        _ref: "document",
        _enum: "ordinal",
        _value: "targetEnum",
      },
    }
  }
  const prefix = "[batchPlay debug]"
  Logger(debug, prefix).log("batchPlay", JSON.stringify(descriptors, " ", 2))
  try {
    const result = await ps.action.batchPlay(
      descriptors,
      removeEmpty({
        synchronousExecution: false,
        modalBehavior: "fail",
        historyStateInfo: historyState,
        ...options,
      })
    )
    result.forEach((item) => {
      if (item.message) {
        Logger(true, prefix).error(">>> Descriptor failed", item.message)
        throw item.message
      }
    })
    return result
  } catch (e) {
    // Logger(true, prefix).info(
    //   "Descriptor failed",
    //   JSON.stringify(descriptors, " ", 2)
    // )
    // Logger(true, prefix).error(e)
    throw e
  }
}

Yes try/catch doesn’t work here. Try “thenning” after batchplay

@Michel77 Why wouldn’t it work?
I am awaiting on batchPlay. If you don’t await, you get the Promise returned that you need to do then on. But if you await, any error inside the promise is usually delivered as an exception.

I do get exception from item.message on the result correctly.

From what i understood you don’t get a descriptor. When thenning batchplay you will.

Let’s say I call

await batchPlay([
  deleteLayer1Descriptor,
  deleteLayer2Descriptor,
  deleteLayer3Descriptor,
  deleteLayer4Descriptor,
])

In the situation where I have layers: [Layer1, Layer3, Layer4]

deleting Layer2 descriptor via batchPlay will fail with delete command is not currently available and this will result in whole chain of descriptors to fail. This will delete Layer1, but will not delete Layer3 and Layer4.

What I want is even if deleteLayer2Descriptor fails, other descriptors in batchPlay to continue and finish.

As Kerri said:

I don’t think there is a mechanism (yet) to continue a batchPlay stack once an error occured.
Maybe you could

  • Validate beforehand whether the layers you want to remove exist
  • Check for any error message in the returned descriptor array. If there is an error, you could theoretically rerun the whole stack, starting at the next descriptor relative to where the error happened.
1 Like

Yes, I took me some time to figure out how to handle errors in batchPlay. Would be great to have this in documentation.

In my code above you can see that I am reading messages in batchPlay result and throw each of the messages. Then I can use the usual mechanism of try/catch when awaiting instead of doing some result analysis and thening.

The tricky case I have now is Copy.
I need to copy a channel that exists into channel with another name, that might exist or not.

export const Copy = () => {
  return {
    build(source, dest) {
      return [
        ...Delete().byName(dest).build(),
        ...Select().build(source),
        ...Duplicate().build(dest)
      ]
    }
  }
}

Delete descriptor can fail if dest channel doesn’t exist and then whole Copy fails.
I can’t run another batchPlay because I need all actions to be in a single batchPlay to have a proper History entry

I went with idea of manually continuing descriptors after failure. Luckily, result contains both successes and failures so it’s easy to find an index where to slice.

const ps = require("photoshop")
import { Logger } from "../../common/logger"
import { removeEmpty } from "../../utils"

export const batchPlay = async (
  descriptors,
  options = { debug: false, continue: false, history: undefined }
) => {
  const prefix = "[batchPlay debug]"
  const { debug, history } = options
  const l = Logger(debug, prefix)
  var historyState = undefined
  if (history) {
    historyState = {
      name: history,
      target: {
        _ref: "document",
        _enum: "ordinal",
        _value: "targetEnum",
      },
    }
  }
  l.log("batchPlay", JSON.stringify(descriptors, " ", 2))
  try {
    const result = await ps.action.batchPlay(
      descriptors,
      removeEmpty({
        synchronousExecution: false,
        modalBehavior: "fail",
        historyStateInfo: historyState,
        ...options,
      })
    )
    result.forEach((item, index) => {
      if (item.message) {
        Logger(true, prefix).error(
          ">>> Descriptor failed",
          descriptors[index],
          item.message
        )
        throw {
          message: item.message,
          continuation: descriptors.slice(index + 1),
        }
      }
    })
    return result
  } catch (payload) {
    if (!options.continue) {
      throw payload.message
    }

    const { continuation } = payload
    l.log("continue with", descriptors)

    return await batchPlay(continuation, options)
  }
}
2 Likes

Here’s a tiny bit optimized (no need to loop to find an error - it’s always last) and a bit improved try {} catch (e) {} logic - return full results with errors and each error contains exact descriptor that yielded it. Also, if option is set to fail on error, error contains successful descriptors result, same error message and failed descriptor

let result

try {
    result = await ps.action.batchPlay(descriptors, options)
    const lastResultItem = result[result.length - 1]

    if (lastResultItem.message) {
        throw {
            message: lastResultItem.message,
            failedDescriptor: descriptors[result.length - 1],
            continuation: descriptors.slice(result.length),
        }
    }

    return result
} catch (payload) {
    if (!options.continue) {
        delete payload.continuation
        result.pop()

        throw {
            succeededDescriptors: result,
            error: payload,
        }
    }

    result[result.length - 1]["failedDescriptor"] = payload.failedDescriptor

    return [
        ...result,
        ...await batchPlay(payload.continuation, options),
    ]
}

Here’s the output of a single run with a few errors:

1 Like