Learn MOAR in Spring ’20 Implementing Promises with Transaction Finalizers

Discover Spring ’20 Release features! We are sharing release highlights for Developers (and Admins), curated and published by Salesforce product experts, as part of Learn MOAR. Complete the trailmix by March 31, 2020 to receive a special community badge and unlock a $10 contribution to FIRST®.

The Queueable interface is my favorite way of managing asynchronous work. But there’s one thing I love even more: the Promise pattern. A while back, I built a Promise library using Queueables! When Chris Peterson started as the Product Manager for Apex, I argued for a native Promise implementation. Instead, his team has brought us the pilot for Transaction Finalizers. Transaction Finalizers enable developers to define what happens when a Queueable job completes. Regardless whether that work takes 2 seconds, or 10. In other words asynchronous flow control, AKA: Promises!

A brief overview of Promises

Promises are a feature of several common programming languages. Simply put, they’re placeholders for a value not yet available. Or, more poetically, the promise of a value. They’re designed to help developers make and maintain understandable asynchronous code. Promises have two parts: a promise generating function, and the follow up, or then action. When that promise object completes its asynchronous work the then action executes. Let’s take a look at an example Javascript Promise and highlight those pieces.

//#1
const promiseGeneratingFunction = (seconds) => new Promise(
  (resolve) => { 
    return setTimeout(resolve, (seconds * 1000)) 
  }
);

promiseGeneratingFunction(3)
  .then(() => {
    //#2
    console.log('3 seconds have passed');
  })

In our example above, the promiseGeneratingFunction returns a Promise object. (Comment #1) The call to promiseGeneratingFunction immediately returns a Promise object. When the asynchronous work completes, the function defined in the .then() method executes. This is not an exhaustive example, but it does illustrate the basics. The .then() method allows you to define what runs when the asynchronous work completes. Regardless whether it takes 2 seconds, or 10. Sound familiar? The one bit of magic with promises is that they return a new promise. This allows developers to chain together promises like this:

//#1
const promiseGeneratingFunction = (seconds) => new Promise(
  (resolve) => setTimeout(resolve, (seconds * 1000))
);

promiseGeneratingFunction(3)
  .then(() => {
    //#2
    console.log('3 seconds have passed');
    return 'foo';
  })
  .then((incoming) => {
    console.log(incoming); // prints 'foo'!
  })

Notice that return statement? That’s passed as input to the next promise.

Transaction Finalizers are the .then() of Queueables.

With that background, explaining what Transaction Finalizers are is pretty easy. They function as the native Apex functional equivalent to Javascript’s .then() method. But explaining what you can do with them takes a bit more work. Not because it’s difficult, but because the sky’s the limit.

Retries, Chaining and Multiplexing OH MY!

This is where Transaction Finalizers get fun. I want to highlight two features of the Finalizer interface – You control the constructor. Because of this, you can pass parameters into the Finalizer and act on them! Secondly, the Finalizer interface gives you access to the FinalizerContext object. Among other things, this object gives you access to the getAsyncApexJobResult() method. This returns an Enum indicating the completion status of the Queueable who’s completion triggered this Finalizer. Either SUCCESS, or UNHANDLED_EXCEPTION. This gives you flow-control on how you want to handle the next stage in your workflow. You could retry a failing Queueable, enqueue a different Queueable, or fire events.

Most importantly, the Finalizer interface gives us an elegant, and reusable way to implement Promises! My original implementation of Promises was nearly 200 lines of code. This implementation, weighs in at less than 50! it has two classes. A Promise class that ensures method name uniformity amongst your classes and implements the Queueable and Database.AllowsCallouts interfaces. Secondly, a Chain class that works as a reusable Finalizer. Here’s that Promise class:

public abstract class Promise implements Queueable, Database.AllowsCallouts {
  public Promise[] promises = new Promise[]{};
  public Object passthrough;
  
  public Promise then(object toAdd){
    promises.add((Promise)toAdd);
    return this;
  }
  
  public Promise(){}

  abstract public void execute();

  public virtual void execute(QueueableContext context){
    execute();
    Finalizer chain = new Chain(this.promises, this.passthrough);
    System.attachFinalizer(chain);
  }
}

Promise is an Abstract class. You must extend Promise and override the execute() method. This is where the body of your asynchronous work goes. This is the only method your class must implement, but that’s not all Promise brings to the table. Promise implements the Queueable interface, and attaches the Chain Finalizer for you. Promise also defines the then() method. This bit of syntactic sugar makes it easy to develop and maintain asynchronous code. You’ll see this in action when we get to our unit test! Now, about that Chain Finalizer class.

public class Chain implements Finalizer {
  Promise[] promises;
  Object passthrough;

  public Chain(Promise[] promises, Object passthrough){
    this.promises = promises;
    this.passthrough = passthrough;
  }

  public void execute(FinalizerContext context){
    Id parentQueueableJobId = context.getAsyncApexJobId();
    switch on context.getAsyncApexJobResult() {
      when SUCCESS {
        if(this.promises.size() > 0){
          Promise next = this.promises.remove(0);
          next.promises = this.promises;
          next.passthrough = passthrough;
          System.enqueueJob(next);
        }
      }
      when UNHANDLED_EXCEPTION {
        System.Debug('Parent Queueable (Job ID: ' + parentQueueableJobId + '): FAILED!');
        System.Debug('Parent Queueable Exception: ' + context.getAsyncApexJobException().getMessage());
      }
    }    
  }
}

The Chain Finalizer handles checking on the status of the just-completed Queueable. In the event it succeeded, Chain enqueues the next one. Additionally, this class maintains the passthrough object. This object allows you to pass data between Queueables, without DML. Before we get into how that comes together in our demo class, let’s look at an architecture diagram.

We start at the top, where some code has enqueued the initial bit of async work. The class defining this work implements the abstract class Promise. Thus, the Promise implementing class gets access to its methods, constructors, and properties. When the system executes the Promise, it calls the Promise class’ execute(QueueableContext context). Here, Promise executes the execute method found in your implementing class’s own execute method. Promise’s execute method also constructs an instance of the Chain class. Note how Promise constructs Chain with the list of promises and the passthrough object. Finally, the Promise execute method calls System.attachFinalizer() with the new Chain instance. When the system has completed executing the async work the system executes the attached Chain instance. The Chain Finalizer calls getAsyncApexJobResult() on Context. If that reports SUCCESS, it pops the next item off the Promises list. This next action has its instances’ Promises and passthrough instance variables set. Finally, it enqueues that next action as a job.

Bringing it all together

Here’s how these come together in a Demo class:

public with sharing class DemoPromise extends Promise {
  Id accountId;

  public DemoPromise(Id accountId){
    this.accountId = accountId;
  }

  public override void execute(){
    Account acct = [SELECT name, shippingStreet FROM Account WHERE id = :this.accountId];
    Integer x = Integer.valueOf(acct.shippingStreet);
    acct.shippingStreet = String.valueOf(x+1);
    if(this.passthrough != null){
      this.passthrough = 1 + (Integer) this.passthrough;
      acct.billingStreet = String.valueOf(this.passthrough);
    }
    update acct;
    System.debug('Async Work Completed!');
  }
}

As you can see, this demo isn’t doing any meaningful work. However, it does enable us to write a meaningful unit test that demonstrates that Promise works as we expect. This test uses the billingStreet and shippingStreet of an account created during @TestSetup. These fields hold a count of the number of times this code has executed.

@isTest
private static void basicE2ETest(){
  Account account = [SELECT Id FROM Account WHERE name = 'TestAccount' LIMIT 1];
  Test.startTest();
  DemoPromise dp = new DemoPromise(account.id);
  dp.passthrough = (Integer) 0;
  dp.then(new DemoPromise(account.id))
    .then(new DemoPromise(account.id));
  System.enqueueJob(dp);
  Test.stopTest();

  Account checkAccount = [SELECT ShippingStreet, billingStreet FROM Account WHERE id = :account.id LIMIT 1];
  System.assertEquals(3, Integer.valueOf(checkAccount.shippingStreet), 
    'Expected 3 instances of the queuable to have been run, each incrementing by 1');
  System.assertEquals(3, Integer.valueOf(checkAccount.billingStreet), 
    'Expected 3 instances of the queuable to have been run, each incrementing by 1');
}

I combined Queueables and Finalizers to build an implementation of the Promise pattern. But that’s just one option for Finalizers! I’m curious to see what YOU will build! Will you harness Queueables, Finalizers and Events to build a robust error logging system? A multiplexer for Finalizers? (Chris Peterson has a lead on that over here). Of course if you’re new to Queueables I want to draw your attention to this Trailhead module: Asynchronous Apex that covers not just Queueables but Future, Batch and Scheduled Apex as well. Hit us up on Twitter @SalesforceDevs with details on what you’ve built!