Skip to content

Commit 3f20721

Browse files
Merge pull request #227228 from hossam-nasr/durable-v4-model
Add Node v4 Programming Model to Durable docs
2 parents d5db367 + ac960de commit 3f20721

12 files changed

+999
-90
lines changed

.openpublishing.publish.config.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,13 @@
233233
{
234234
"path_to_root": "azure-functions-durable-js",
235235
"url": "https://github.com/Azure/azure-functions-durable-js",
236-
"branch": "main",
236+
"branch": "v2.x",
237+
"branch_mapping": {}
238+
},
239+
{
240+
"path_to_root": "azure-functions-durable-js-v3",
241+
"url": "https://github.com/Azure/azure-functions-durable-js",
242+
"branch": "v3.x",
237243
"branch_mapping": {}
238244
},
239245
{

articles/azure-functions/durable/TOC.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
- name: Create durable function - JavaScript
2727
displayName: get started, chaining
2828
href: quickstart-js-vscode.md
29+
- name: Create durable function - TypeScript
30+
displayName: get started, chaining
31+
href: quickstart-ts-vscode.md
2932
- name: Create durable function - Python
3033
displayName: get started, chaining
3134
href: quickstart-python-vscode.md

articles/azure-functions/durable/durable-functions-cloud-backup.md

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@
22
title: Fan-out/fan-in scenarios in Durable Functions - Azure
33
description: Learn how to implement a fan-out-fan-in scenario in the Durable Functions extension for Azure Functions.
44
ms.topic: conceptual
5-
ms.date: 11/02/2019
5+
ms.date: 02/14/2023
66
ms.author: azfuncdf
77
---
88

99
# Fan-out/fan-in scenario in Durable Functions - Cloud backup example
1010

1111
*Fan-out/fan-in* refers to the pattern of executing multiple functions concurrently and then performing some aggregation on the results. This article explains a sample that uses [Durable Functions](durable-functions-overview.md) to implement a fan-in/fan-out scenario. The sample is a durable function that backs up all or some of an app's site content into Azure Storage.
1212

13+
> [!NOTE]
14+
> The new programming model for authoring Functions in Node.js (V4) is currently in preview. Compared to the current model, the new experience is designed to be more idiomatic and intuitive for JavaScript and TypeScript developers. To learn more, see the Azure Functions Node.js [developer guide](../functions-reference-node.md?pivots=nodejs-model-v4).
15+
>
16+
> In the following code snippets, JavaScript (PM4) denotes programming model V4, the new experience.
17+
1318
[!INCLUDE [durable-functions-prerequisites](../../../includes/durable-functions-prerequisites.md)]
1419

1520
## Scenario overview
@@ -50,7 +55,7 @@ Notice the `await Task.WhenAll(tasks);` line. All the individual calls to the `E
5055

5156
After awaiting from `Task.WhenAll`, we know that all function calls have completed and have returned values back to us. Each call to `E2_CopyFileToBlob` returns the number of bytes uploaded, so calculating the sum total byte count is a matter of adding all those return values together.
5257

53-
# [JavaScript](#tab/javascript)
58+
# [JavaScript (PM3)](#tab/javascript-v3)
5459

5560
The function uses the standard *function.json* for orchestrator functions.
5661

@@ -67,6 +72,19 @@ Notice the `yield context.df.Task.all(tasks);` line. All the individual calls to
6772
6873
After yielding from `context.df.Task.all`, we know that all function calls have completed and have returned values back to us. Each call to `E2_CopyFileToBlob` returns the number of bytes uploaded, so calculating the sum total byte count is a matter of adding all those return values together.
6974

75+
# [JavaScript (PM4)](#tab/javascript-v4)
76+
77+
Here is the code that implements the orchestrator function:
78+
79+
:::code language="javascript" source="~/azure-functions-durable-js-v3/samples-js/functions/backupSiteContent.js" range="1,4,7-35":::
80+
81+
Notice the `yield context.df.Task.all(tasks);` line. All the individual calls to the `copyFileToBlob` function were *not* yielded, which allows them to run in parallel. When we pass this array of tasks to `context.df.Task.all`, we get back a task that won't complete *until all the copy operations have completed*. If you're familiar with [`Promise.all`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) in JavaScript, then this is not new to you. The difference is that these tasks could be running on multiple virtual machines concurrently, and the Durable Functions extension ensures that the end-to-end execution is resilient to process recycling.
82+
83+
> [!NOTE]
84+
> Although Tasks are conceptually similar to JavaScript promises, orchestrator functions should use `context.df.Task.all` and `context.df.Task.any` instead of `Promise.all` and `Promise.race` to manage task parallelization.
85+
86+
After yielding from `context.df.Task.all`, we know that all function calls have completed and have returned values back to us. Each call to `copyFileToBlob` returns the number of bytes uploaded, so calculating the sum total byte count is a matter of adding all those return values together.
87+
7088
# [Python](#tab/python)
7189

7290
The function uses the standard *function.json* for orchestrator functions.
@@ -96,7 +114,7 @@ The helper activity functions, as with other samples, are just regular functions
96114

97115
[!code-csharp[Main](~/samples-durable-functions/samples/precompiled/BackupSiteContent.cs?range=44-54)]
98116

99-
# [JavaScript](#tab/javascript)
117+
# [JavaScript (PM3)](#tab/javascript-v3)
100118

101119
The *function.json* file for `E2_GetFileList` looks like the following:
102120

@@ -108,6 +126,14 @@ And here is the implementation:
108126

109127
The function uses the `readdirp` module (version 2.x) to recursively read the directory structure.
110128

129+
# [JavaScript (PM4)](#tab/javascript-v4)
130+
131+
Here is the implementation of the `getFileList` activity function:
132+
133+
:::code language="javascript" source="~/azure-functions-durable-js-v3/samples-js/functions/backupSiteContent.js" range="1,3,7,36-48":::
134+
135+
The function uses the `readdirp` module (version `3.x`) to recursively read the directory structure.
136+
111137
# [Python](#tab/python)
112138

113139
The *function.json* file for `E2_GetFileList` looks like the following:
@@ -134,7 +160,7 @@ And here is the implementation:
134160
135161
The function uses some advanced features of Azure Functions bindings (that is, the use of the [`Binder` parameter](../functions-dotnet-class-library.md#binding-at-runtime)), but you don't need to worry about those details for the purpose of this walkthrough.
136162

137-
# [JavaScript](#tab/javascript)
163+
# [JavaScript (PM3)](#tab/javascript-v3)
138164

139165
The *function.json* file for `E2_CopyFileToBlob` is similarly simple:
140166

@@ -144,6 +170,12 @@ The JavaScript implementation uses the [Azure Storage SDK for Node](https://gith
144170

145171
:::code language="javascript" source="~/azure-functions-durable-js/samples/E2_CopyFileToBlob/index.js":::
146172

173+
# [JavaScript (PM4)](#tab/javascript-v4)
174+
175+
The JavaScript implementation of `copyFileToBlob` uses an Azure Storage output binding to upload the files to Azure Blob storage.
176+
177+
:::code language="javascript" source="~/azure-functions-durable-js-v3/samples-js/functions/backupSiteContent.js" range="1-2,5-6,8-9,50-68":::
178+
147179
# [Python](#tab/python)
148180

149181
The *function.json* file for `E2_CopyFileToBlob` is similarly simple:

articles/azure-functions/durable/durable-functions-error-handling.md

Lines changed: 97 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: Handling errors in Durable Functions - Azure
33
description: Learn how to handle errors in the Durable Functions extension for Azure Functions.
44
ms.topic: conceptual
5-
ms.date: 12/07/2022
5+
ms.date: 02/14/2023
66
ms.author: azfuncdf
77
ms.devlang: csharp, javascript, powershell, python, java
88
---
@@ -11,6 +11,11 @@ ms.devlang: csharp, javascript, powershell, python, java
1111

1212
Durable Function orchestrations are implemented in code and can use the programming language's built-in error-handling features. There really aren't any new concepts you need to learn to add error handling and compensation into your orchestrations. However, there are a few behaviors that you should be aware of.
1313

14+
> [!NOTE]
15+
> The new programming model for authoring Functions in Node.js (V4) is currently in preview. Compared to the current model, the new experience is designed to be more idiomatic and intuitive for JavaScript and TypeScript developers. To learn more, see the Azure Functions Node.js [developer guide](../functions-reference-node.md?pivots=nodejs-model-v4).
16+
>
17+
> In the following code snippets, JavaScript (PM4) denotes programming model V4, the new experience.
18+
1419
## Errors in activity functions
1520

1621
Any exception that is thrown in an activity function is marshaled back to the orchestrator function and thrown as a `FunctionFailedException`. You can write error handling and compensation code that suits your needs in the orchestrator function.
@@ -95,41 +100,63 @@ public static async Task Run(
95100
}
96101
```
97102

98-
# [JavaScript](#tab/javascript)
103+
# [JavaScript (PM3)](#tab/javascript-v3)
99104

100105
```javascript
101106
const df = require("durable-functions");
102107

103-
module.exports = df.orchestrator(function*(context) {
108+
module.exports = df.orchestrator(function* (context) {
104109
const transferDetails = context.df.getInput();
105110

106-
yield context.df.callActivity("DebitAccount",
107-
{
108-
account = transferDetails.sourceAccount,
109-
amount = transferDetails.amount,
110-
}
111-
);
111+
yield context.df.callActivity("DebitAccount", {
112+
account: transferDetails.sourceAccount,
113+
amount: transferDetails.amount,
114+
});
112115

113116
try {
114-
yield context.df.callActivity("CreditAccount",
115-
{
116-
account = transferDetails.destinationAccount,
117-
amount = transferDetails.amount,
118-
}
119-
);
117+
yield context.df.callActivity("CreditAccount", {
118+
account: transferDetails.destinationAccount,
119+
amount: transferDetails.amount,
120+
});
121+
} catch (error) {
122+
// Refund the source account.
123+
// Another try/catch could be used here based on the needs of the application.
124+
yield context.df.callActivity("CreditAccount", {
125+
account: transferDetails.sourceAccount,
126+
amount: transferDetails.amount,
127+
});
120128
}
121-
catch (error) {
129+
})
130+
```
131+
# [JavaScript (PM4)](#tab/javascript-v4)
132+
133+
```javascript
134+
const df = require("durable-functions");
135+
136+
df.app.orchestration("transferFunds", function* (context) {
137+
const transferDetails = context.df.getInput();
138+
139+
yield context.df.callActivity("debitAccount", {
140+
account: transferDetails.sourceAccount,
141+
amount: transferDetails.amount,
142+
});
143+
144+
try {
145+
yield context.df.callActivity("creditAccount", {
146+
account: transferDetails.destinationAccount,
147+
amount: transferDetails.amount,
148+
});
149+
} catch (error) {
122150
// Refund the source account.
123151
// Another try/catch could be used here based on the needs of the application.
124-
yield context.df.callActivity("CreditAccount",
125-
{
126-
account = transferDetails.sourceAccount,
127-
amount = transferDetails.amount,
128-
}
129-
);
152+
yield context.df.callActivity("creditAccount", {
153+
account: transferDetails.sourceAccount,
154+
amount: transferDetails.amount,
155+
});
130156
}
131157
});
132158
```
159+
133160
# [Python](#tab/python)
134161

135162
```python
@@ -248,7 +275,7 @@ public static async Task Run([OrchestrationTrigger] TaskOrchestrationContext con
248275
}
249276
```
250277

251-
# [JavaScript](#tab/javascript)
278+
# [JavaScript (PM3)](#tab/javascript-v3)
252279

253280
```javascript
254281
const df = require("durable-functions");
@@ -266,6 +293,23 @@ module.exports = df.orchestrator(function*(context) {
266293
});
267294
```
268295

296+
# [JavaScript (PM4)](#tab/javascript-v4)
297+
298+
```javascript
299+
const df = require("durable-functions");
300+
301+
df.app.orchestration("callActivityWithRetry", function* (context) {
302+
const firstRetryIntervalInMilliseconds = 5000;
303+
const maxNumberOfAttempts = 3;
304+
305+
const retryOptions = new df.RetryOptions(firstRetryIntervalInMilliseconds, maxNumberOfAttempts);
306+
307+
yield context.df.callActivityWithRetry("flakyFunction", retryOptions);
308+
309+
// ...
310+
});
311+
```
312+
269313
# [Python](#tab/python)
270314

271315
```python
@@ -372,7 +416,11 @@ catch (TaskFailedException)
372416
}
373417
```
374418

375-
# [JavaScript](#tab/javascript)
419+
# [JavaScript (PM3)](#tab/javascript-v3)
420+
421+
JavaScript doesn't currently support custom retry handlers. However, you still have the option of implementing retry logic directly in the orchestrator function using loops, exception handling, and timers for injecting delays between retries.
422+
423+
# [JavaScript (PM4)](#tab/javascript-v4)
376424

377425
JavaScript doesn't currently support custom retry handlers. However, you still have the option of implementing retry logic directly in the orchestrator function using loops, exception handling, and timers for injecting delays between retries.
378426

@@ -474,7 +522,7 @@ public static async Task<bool> Run([OrchestrationTrigger] TaskOrchestrationConte
474522
}
475523
```
476524

477-
# [JavaScript](#tab/javascript)
525+
# [JavaScript (PM3)](#tab/javascript-v3)
478526

479527
```javascript
480528
const df = require("durable-functions");
@@ -498,6 +546,30 @@ module.exports = df.orchestrator(function*(context) {
498546
});
499547
```
500548

549+
# [JavaScript (PM4)](#tab/javascript-v4)
550+
551+
```javascript
552+
const df = require("durable-functions");
553+
const { DateTime } = require("luxon");
554+
555+
df.app.orchestration("timerOrchestrator", function* (context) {
556+
const deadline = DateTime.fromJSDate(context.df.currentUtcDateTime).plus({ seconds: 30 });
557+
558+
const activityTask = context.df.callActivity("flakyFunction");
559+
const timeoutTask = context.df.createTimer(deadline.toJSDate());
560+
561+
const winner = yield context.df.Task.any([activityTask, timeoutTask]);
562+
if (winner === activityTask) {
563+
// success case
564+
timeoutTask.cancel();
565+
return true;
566+
} else {
567+
// timeout case
568+
return false;
569+
}
570+
});
571+
```
572+
501573
# [Python](#tab/python)
502574

503575
```python

0 commit comments

Comments
 (0)