Files

212 lines
5.6 KiB
Markdown

# Durable Task Scheduler — JavaScript
## Learn More
- [Durable Task Scheduler documentation](https://learn.microsoft.com/azure/durable-task-scheduler/)
- [Durable Functions JavaScript guide](https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-overview?tabs=javascript)
## Durable Functions Setup
### Required npm Packages
```json
{
"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](https://www.npmjs.com/package/durable-functions) for the latest stable release.
### host.json
```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
```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
```javascript
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
```javascript
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
```javascript
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
```javascript
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
```javascript
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):
```javascript
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);
```