TS Temporal - Calling asynchronous activities from setHandler

Is there any best practices as to what should and shouldnt be put in setHandler when processing signals ? Can we have asynchrounous activities in them and also switch/case as well as (await condition(() => … ?

Can we have asynchrounous activities in them and also switch/case as well as (await condition(() => … ?

Yes, this is correctly supported by the Temporal TS SDK (eg. there is no risk of non-determinism errors due to calling activities or sleeping directly from a signal handler).

However, you should be very cautious about such things, as it tends to make workflow code error prone, and sensible to various timing issues. In particular, you need to understand that in-progress async handlers will not prevent your workflow from completing when execution reaches the end of your workflow’s main function.

For those reasons, we strongly recommend designing your workflows so your signal handlers don’t need to be async. A very common approach for that is to maintain an internal task queue inside your workflow. Signal handlers can then push tasks to that internal queue. Actual execution of tasks can be performed from the workflow’s main function.

Here is one possible implementation of this pattern:

// Make sure Request is serializable to JSON (ie. no function, no promises, etc)
type Request = { ... }

// Entity workflow pattern with serialization of request
// (ie. only one request is processed at a time)
export async function myWorkflow(requests: Request[] = []): Promise<void> {
  setHandler(mySignal, (input: Request) => {
    requests.push(input);
    // Don't await here. Otherwise, the Workflow may complete before the promise completes.
  });

  while (!workflowInfo().continueAsNewSuggested) {
    const timeSinceStart = Date.now() - workflowInfo().runStartTime.getTime();
    const shouldProcessMore = await condition(() => requests.length > 0, ms('24h') - timeSinceStart);
    if (!shouldProcessMore) break;

    const request = requests.shift();

    // Process request as appropriate
    await handleSingleRequest(request);
  }

  // Huge histories are bad for performance, so we switch to a new workflow execution whenever
  // history grows too much. When that happens, we forward any outstanding requests to the
  // next execution.
  await continueAsNew(requests);
}

function handleSingleRequest(request: Request): Promise<void> {
  // It's ok to await here
}
1 Like