Return value for workflows

Looking at the TypeScript examples, workflows are returning unstructured message strings, usually returned by activities. For example, in nextjs-ecommerce-oneclick:

export async function OneClickBuy(itemId: string) {
  const itemToBuy = itemId;
  let purchaseState: PurchaseState = 'PURCHASE_PENDING';
  wf.setHandler(cancelPurchase, () => void (purchaseState = 'PURCHASE_CANCELED'));
  wf.setHandler(purchaseStateQuery, () => purchaseState);
  if (await wf.condition(() => purchaseState === 'PURCHASE_CANCELED', '5s')) {
    return await canceledPurchase(itemToBuy);
  } else {
    purchaseState = 'PURCHASE_CONFIRMED';
    return await checkoutItem(itemToBuy);
  }
}

Also don’t see how these values are accessed or used on the client side. For example, in the above example, the return value is not accessed or used.

await client.workflow.start(OneClickBuy, {
  taskQueue: 'ecommerce-oneclick',
  workflowId: transactionId,
  args: [itemId],
});

res.status(200).json({ ok: true });

Questions:

  1. What are the best practices around determining the return value from a workflow?
  2. Would it be a good pattern to at least return the current workflow state so that the client can enable the next set of valid actions. Currently my client is using await workflow.query('workflowState') to get the current state, but that seems like an unnecessary round trip. Of course, I realize that the workflow state can change anytime, but this approach might cover some use cases without a round trip.
  3. How does the client access the return value (in TypeScript)?

Hello @nareshbhatia

Looking at the TypeScript examples, workflows are returning unstructured message strings, usually returned by activities.

You can return any type of object it has to be serializable. please see:

  1. What are the best practices around determining the return value from a workflow?

I think this is closely related to your business. What do you need your workflow function to return?

  1. Would it be a good pattern to at least return the current workflow state so that the client can enable the next set of valid actions. Currently my client is using await workflow.query('workflowState') to get the current state, but that seems like an unnecessary round trip. Of course, I realize that the workflow state can change anytime, but this approach might cover some use cases without a round trip.

To get the internal workflow state the recommendation is to use queries for running workflows. Build durable applications with Temporal | Temporal Documentation, and as you have mentioned you can return it as part of the workflow result.

  1. How does the client access the return value (in TypeScript)?

const result: Result = await handle.result()

Have a look at this example https://github.com/temporalio/samples-typescript/blob/main/ejson/src/client.ts#L32

Just in case it helps, with handle.describe() you can get other useful information, like the workflow status (completed, running…) : await handle.describe()).status

Antonio

1 Like

Thank you so much, @antonio.perez for a detailed response. Can’t wait to read and try all this. Will follow up, if I have any questions.

There’s also a client.workflow.execute method that’s effectively shorthand for:

const handle = await client.workflow.start(...)
const result = await handle.result()

Hi @bergundy, does that amount to a single call to the server or two?

There’s also a client.workflow.execute method that’s effectively shorthand for:

const handle = await client.workflow.start(...)
const result = await handle.result()

It results in (at least) two server calls.
It’s just syntactic sugar.

I say “at least” because getting the result is done via long polling the server and it depends how long it takes the workflow to complete.

FTR, if you want to get the result of an already running workflow, you can get a handle to it with:

const handle = client.workflow.getHandle(workflowId)

This method does not call the server and thus does not verify that there’s an existing workflow for the given ID.

In many use cases, a result of the workflow should be pushed to some external system. In this case, you can use an activity that executes at the end of the workflow and sends the result.