Files

5.6 KiB

Durable Task Scheduler — JavaScript

Learn More

Durable Functions Setup

Required npm Packages

{
  "dependencies": {
    "@azure/functions": "^4.0.0",
    "durable-functions": "^3.0.0"
  }
}

💡 Finding latest versions: Run npm view durable-functions version or check npmjs.com/package/durable-functions for the latest stable release.

host.json

{
  "version": "2.0",
  "extensions": {
    "durableTask": {
      "hubName": "default",
      "storageProvider": {
        "type": "durabletask-scheduler",
        "connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING"
      }
    }
  },
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[4.*, 5.0.0)"
  }
}

local.settings.json

{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None"
  }
}

Minimal Example

const { app } = require("@azure/functions");
const df = require("durable-functions");

// Activity
df.app.activity("sayHello", {
  handler: (city) => `Hello ${city}!`,
});

// Orchestrator
df.app.orchestration("myOrchestration", function* (context) {
  const result1 = yield context.df.callActivity("sayHello", "Tokyo");
  const result2 = yield context.df.callActivity("sayHello", "Seattle");
  return `${result1}, ${result2}`;
});

// HTTP Starter
app.http("HttpStart", {
  route: "orchestrators/{orchestrationName}",
  methods: ["POST"],
  authLevel: "function",
  extraInputs: [df.input.durableClient()],
  handler: async (request, context) => {
    const client = df.getClient(context);
    const instanceId = await client.startNew(request.params.orchestrationName);
    return client.createCheckStatusResponse(request, instanceId);
  },
});

Workflow Patterns

Fan-Out/Fan-In

df.app.orchestration("fanOutFanIn", function* (context) {
  const cities = ["Tokyo", "Seattle", "London", "Paris", "Berlin"];

  // Fan-out: schedule all activities in parallel
  const tasks = cities.map((city) => context.df.callActivity("sayHello", city));

  // Fan-in: wait for all to complete
  const results = yield context.df.Task.all(tasks);
  return results;
});

Human Interaction

df.app.orchestration("approvalWorkflow", function* (context) {
  yield context.df.callActivity("sendApprovalRequest", context.df.getInput());

  // Wait for approval event with timeout
  const expiration = new Date(context.df.currentUtcDateTime);
  expiration.setDate(expiration.getDate() + 3);

  const approvalTask = context.df.waitForExternalEvent("ApprovalEvent");
  const timeoutTask = context.df.createTimer(expiration);

  const winner = yield context.df.Task.any([approvalTask, timeoutTask]);

  if (winner === approvalTask) {
    return approvalTask.result ? "Approved" : "Rejected";
  }
  return "Timed out";
});

Orchestration Determinism

❌ NEVER ✅ ALWAYS USE
new Date() context.df.currentUtcDateTime
Math.random() Pass random values from activities
setTimeout() context.df.createTimer()
Direct I/O, HTTP, database context.df.callActivity()

Replay-Safe Logging

df.app.orchestration("myOrchestration", function* (context) {
  if (!context.df.isReplaying) {
    console.log("Started");  // Only logs once, not on replay
  }
  const result = yield context.df.callActivity("myActivity", "input");
  return result;
});

Error Handling & Retry

df.app.orchestration("workflowWithRetry", function* (context) {
  const retryOptions = new df.RetryOptions(5000, 3); // firstRetryInterval, maxAttempts
  retryOptions.backoffCoefficient = 2.0;
  retryOptions.maxRetryIntervalInMilliseconds = 60000;

  try {
    const result = yield context.df.callActivityWithRetry(
      "unreliableService",
      retryOptions,
      context.df.getInput()
    );
    return result;
  } catch (ex) {
    context.df.setCustomStatus({ error: ex.message });
    yield context.df.callActivity("compensationActivity", context.df.getInput());
    return "Compensated";
  }
});

Durable Task SDK (Non-Functions)

For applications running outside Azure Functions (containers, VMs, Azure Container Apps, Azure Kubernetes Service):

const { createAzureManagedWorkerBuilder, createAzureManagedClient } = require("@microsoft/durabletask-js-azuremanaged");

const connectionString = "Endpoint=http://localhost:8080;Authentication=None;TaskHub=default";

// Activity
const sayHello = async (_ctx, name) => `Hello ${name}!`;

// Orchestrator
const myOrchestration = async function* (ctx, name) {
  const result = yield ctx.callActivity(sayHello, name);
  return result;
};

async function main() {
  // Worker
  const worker = createAzureManagedWorkerBuilder(connectionString)
    .addOrchestrator(myOrchestration)
    .addActivity(sayHello)
    .build();

  await worker.start();

  // Client
  const client = createAzureManagedClient(connectionString);
  const instanceId = await client.scheduleNewOrchestration("myOrchestration", "World");
  const state = await client.waitForOrchestrationCompletion(instanceId, true, 30);
  console.log("Output:", state.serializedOutput);

  await client.stop();
  await worker.stop();
}

main().catch(console.error);