3

I have two asynchronous tasks that need to get completed - say they're "make a pie" (make) and "bake a pie" (bake). I also need to cleanup after everything's done - "clean the kitchen" (cleanup). bake depends on make, and I want to cleanup when things are done. However, there are a few complications:

  • If some nice person has already made the pie, all I have to do is bake.
  • If a problem happens during make - maybe I get tired of this whole pie business - I need to cleanup.
  • If a problem happens during bake - the oven explodes - I also need to cleanup.

My first pass looks like something like this:

func pieTime()
    if !pie.isMade()
        make()
    else if !pie.isBaked()
        bake()
    else
        cleanup(null)

func make()
    makeThePie(completion: {
        if pie.hasError()
            cleanup(error)
        else
            bake()
    })

func bake()
    bakeThePie(completion: {
        if pie.hasError()
            cleanup(error)
        else
            cleanup(null)
    })

func cleanup(error)
    if error != null
        shout("what the ***: %s", error.string())
    destroy(pie)

This isn't too bad, but my main problem with this approach is that there is no single exit point from the flow. cleanup gets called from a bunch of places. As the error handling gets more complicated, and you add more asynchronous calls to the chain, there are more and more exit points from the flow to keep track of. If I miss calling cleanup at any of them, the result is an unusable kitchen.

Basically, I'm looking for something like a do/catch/finally for asynchronous calls. The only thing I could come up with is a queue - queue up only the tasks you need and queue cleanup at the end - but it seems a bit too heavyweight for just two tasks, such as this instance. Is there a well-established pattern for this problem?

  • Yes, do-catch-finally is exactly the pattern you are looking for. If you could be a bit more specific about the language you are implementing this in, we can tell how to apply the pattern also to asynchronous functions. – Bergi Apr 22 '17 at 15:21
  • I was hoping for a language-agnostic answer, but if it is important, I am working in objective-c. – Chaosed0 Apr 22 '17 at 17:03
6

You can use a promise chain like the idiomatic q one, roughly:

doSomethingThatReturnsAPromise
    .then(function (result, error) {
        if (error) {
            [the previous command failed in some way; handle it or throw.]
        }
    })
    .then([same again])
    […]
    .finally(function() {
        [global cleanup]
    })

The main difference from a tree of calls is that it's very intuitive to follow - there's no question of what the entry point is, there's no question of the sequence of the steps, and the cleanup happens unconditionally once the sequence is done.

| improve this answer | |
  • Seems same as OP's first approach – Fabio Apr 21 '17 at 18:40
  • I guess the difference is the finally - I didn't know promises supported this. I'm actually in Objective-C, but there is apparently a library for this which looks pretty much like what I need. – Chaosed0 Apr 21 '17 at 22:13
3

Why not

  • implement functions bake and make without any calls to cleanUp (but coherent error signaling like a specific exception)

  • implement a functional wrapper (a "decorator") which takes an arbitrary function as parameter, runs it asynchronously, catches any error exception and calls cleanup accordingly?

Then the place in code where bake and make (and other equivalent tasks) are started just have to use the wrapper to run the tasks. The final cleanup(null) should not be called by bake directly as well, but by the caller (for example, when he gets the event for all tasks having completed without any error).

| improve this answer | |
  • This is the solution I ended up going with because it was less invasive. However, I feel that the selected answer is the one I would implement if given no constraints. – Chaosed0 Apr 24 '17 at 19:21
2

The cleanup method can serve as a decorator for the make and bake command chain. In this case, the decorator serves the purpose of cleaning up. As the command chain executes, any error will move up the chain until the cleanup catches and cleans.

There is no reason to add in async as part of this chain since baking depends on making and error handling also depends on the result of making and possibly baking. If you wanted to run the entire chain async (making and baking several pies at once, then at the very top I would make an async callout and then look at the task result of the entire call chain as opposed to individual call outs.

Here's the chain (In C#):

var command = new PieCleanup(new MakePie(new BakePie()));
//You could run this async if many pies to make and bake
command.Execute(new Pie());

The Cleanup Decorator:

    public class PieCleanup : ICommand<Pie>
    {
        private readonly ICommand<Pie> _command;

        public PieCleanup(ICommand<Pie> command)
        {
            _command = command;
        }

        public void Execute(Pie pie)
        {
            try
            {
                _command.Execute(pie);
            }
            catch (Exception)
            {
                //TODO:  Cleanup
            }
        }
    }

Make Pie:

 public class MakePie : ICommand<Pie>
    {
        private readonly ICommand<Pie> _command;

        public MakePie(ICommand<Pie> command)
        {
            _command = command;
        }

        public void Execute(Pie pie)
        {
            //TODO: Make Pie
            //Now Run the next command (bake)
            _command.Execute(pie);
        }
    }

Bake Pie:

 public class BakePie : ICommand<Pie>
    {
        public void Execute(Pie pie)
        {
            //TODO: Bake Pie
        }
    }
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.