Step Functions : apply try-catch to a block of states

In my last post we talked about how we can imple­ment sem­a­phores with Step Func­tions. Anoth­er com­mon sce­nario that many peo­ple have is to han­dle errors from a block of states like we’re used to with a try-catch block.

try {
step1()
step2()
step3()
} catch (States.Timeout) {
...
} catch (States.ALL) {
...
}

With Step Func­tions, you can use Retry and Catch claus­es to han­dle errors from Task states. There are a num­ber of pre­de­fined sys­tem errors, and you can also han­dle cus­tom errors that are thrown by your Lamb­da func­tions.

You can do this by adding the same Catch clause to each of the Task states.

"Catch": [
{
"ErrorEquals": [ "States.ALL" ],
"Next": "NotifyError"
}
]

How­ev­er, this approach requires you to add the same boil­er­plate to every Task state. As your error han­dling strat­e­gy, or the state machine itself becomes more com­plex, this becomes a main­te­nance headache.

For­tu­nate­ly, both Retry and Catch can be used on Parallel states too!

Even if you’re not look­ing to per­form tasks in par­al­lel, you can still use it to sim­pli­fy your error han­dling.

In this case, if I wrap Step1, Step2 and Step3 into a sin­gle branch inside a Parallel state, then I can catch unhan­dled errors from any of the steps with one Catch clause.

{
"StartAt": "Try",
"States": {
"Try": {
"Type": "Parallel",
"Branches": [
{
"StartAt": "Step1",
"States": {
"Step1": {
"Type": "Task",
"Resource": "...",
"Next": "Step2"
},
"Step2": {
"Type": "Task",
"Resource": "...",
"Next": "Step3"
},
"Step3": {
"Type": "Task",
"Resource": "...",
"End": true
}
}
}
],
"Catch": [
{
"ErrorEquals": [ "States.ALL" ],
"Next": "NotifyError"
}
],
"Next": "NotifySuccess"
},
...
}

One final caveat with this approach is that, a Parallel state wraps the out­put from its branch­es into an array. So if sub­se­quent states?—?such as the NotifySuccess state in the exam­ple above?—?wants to use the out­put from Step3 then it’ll have to take that into con­sid­er­a­tion.

What you can do instead, is to add a Pass state to unwrap the array, like this:

"UnwrapOutput": {
"Type": "Pass",
"InputPath": "$[0]",
"Next": "NotifySuccess"
}

This tech­nique is use­ful when you want to apply the same error han­dling to block of states with­out hav­ing to resort­ing to boil­er­plates.

You can add Retry clause to the Parallel state to retry the entire block (i.e. from Step1, even if Step3 errored). You can also add Retry and Catch for indi­vid­ual states to mix things up too.

So that’s it, a nice and short post to share with you a sim­ple tech­nique that I have found use­ful with Step Func­tions.

I have been spend­ing a fair bit of time with Step Func­tions and enjoy­ing the ser­vice. Let me know in the com­ments if you have use cas­es that you find dif­fi­cult to imple­ment with Step Func­tions, I would love to hear what oth­ers are doing with it.


Like what you’re read­ing? Check out my video course Pro­duc­tion-Ready Server­less and learn the essen­tials of how to run a server­less appli­ca­tion in pro­duc­tion.

We will cov­er top­ics includ­ing:

  • authen­ti­ca­tion & autho­riza­tion with API Gate­way & Cog­ni­to
  • test­ing & run­ning func­tions local­ly
  • CI/CD
  • log aggre­ga­tion
  • mon­i­tor­ing best prac­tices
  • dis­trib­uted trac­ing with X-Ray
  • track­ing cor­re­la­tion IDs
  • per­for­mance & cost opti­miza­tion
  • error han­dling
  • con­fig man­age­ment
  • canary deploy­ment
  • VPC
  • secu­ri­ty
  • lead­ing prac­tices for Lamb­da, Kine­sis, and API Gate­way

You can also get 40% off the face price with the code ytcui. Hur­ry though, this dis­count is only avail­able while we’re in Manning’s Ear­ly Access Pro­gram (MEAP).