# Invoking functions from other functions

> Run another Inngest function from a step with InvokeAsync, by its class or its id, pass it data, get its typed result back, and handle failures and timeouts.

Web page: https://damikun.github.io/inngest-dotnet/guides/invoking-functions/

`InvokeAsync` runs another function and waits for its output, as a step. The other function runs as a run of its
own, with its own retries, flow control and history, and its output comes back as the step's result.

```csharp
var score = await context.Step.InvokeAsync<ScoreAccount, AccountScore>("score-acme",
    new ScoreRequest("acme"), timeout: TimeSpan.FromMinutes(5));

context.Logger.LogInformation("acme scored {Score}", score.Score);
```

`ScoreAccount` is the class of the function to run. The step looks up the id the app registered it under, from
options in code or from its attributes, and the compiler checks that `ScoreAccount` returns an `AccountScore`.

## The invoked function

Any function can be invoked. Give it a result type with `InngestFunction<TEventData, TResult>`, and one that should
only ever be invoked has no trigger:

```csharp title="ScoreAccount.cs"
using Inngest;

public sealed record ScoreRequest(string AccountId);

public sealed record AccountScore(string AccountId, int Score);

public sealed class ScoreAccount(IScoring scoring) : InngestFunction<ScoreRequest, AccountScore>
{
    public override async Task<AccountScore> ExecuteAsync(FunctionContext<ScoreRequest> context, CancellationToken cancellationToken)
    {
        var accountId = context.Event.Data.AccountId;
        var score = await context.Step.RunAsync("compute-score", ct => scoring.ScoreAsync(accountId, ct));
        return new AccountScore(accountId, score);
    }
}

public interface IScoring
{
    Task<int> ScoreAsync(string accountId, CancellationToken cancellationToken);
}
```

### Attributes

```csharp
[InngestFunction("score-account")]
```

The attribute goes on `ScoreAccount`, and `inngest.AddFunction<ScoreAccount>()` registers it.

### Code first

```csharp
inngest.AddFunction<ScoreAccount>(new FunctionOptions { Id = "score-account", Triggers = [] });
```

Inngest starts the invoked run with an `inngest/function.invoked` event whose `data` is the data you passed, so the
invoked function reads it as its event data. Its return value is what `InvokeAsync` returns.

A class that derives from `InngestFunction<TEventData>` returns `object?`, so a caller can only read its result as
`object`. Give it a result type, or invoke it by its id.

## By id

To invoke a function of another app, a delegate, or a function whose class the caller cannot reference, name it by
the id it was registered with. The type argument is then only the type to read its output as:

```csharp
var score = await context.Step.InvokeAsync<AccountScore>("score-acme",
    new InvokeOptions("score-account") { Data = new { accountId = "acme" }, Timeout = TimeSpan.FromMinutes(5) });
```

## Options

| Option | Meaning |
| - | - |
| `data` | The invoked run's event data. Serialized with the app's serializer options. In `InvokeOptions`, `Data`. |
| `timeout` | How long to wait for the invoked function. When it passes, the step fails; the invoked run keeps going. In `InvokeOptions`, `Timeout`. |
| `FunctionId` | `InvokeOptions` only: the id of the function to run, as registered. |
| `AppId` | `InvokeOptions` only: the app the function belongs to. Defaults to the current app, so set it only to invoke a function of another app in the same Inngest environment. |
| `User` | `InvokeOptions` only: optional user data for the invoked run's event. |

Without a timeout Inngest waits a long time; see
[step.invoke](https://www.inngest.com/docs/durable-execution/primitives/step-invoke) in the Inngest docs for its
default.

## Fan-out with results

Invoke one function per item and await them together. Each invocation is a separate run, so each item gets its own
retries, and the invoked function's concurrency limit caps how many run at once:

```csharp
string[] accountIds = ["acme", "globex", "initech"];

var scores = await Task.WhenAll(accountIds.Select(accountId =>
    context.Step.InvokeAsync<ScoreAccount, AccountScore>($"score-{accountId}",
        new ScoreRequest(accountId), timeout: TimeSpan.FromMinutes(5))));

await context.Step.SendEventAsync("announce-scores",
    new InngestEvent("reports/scores.ready", new { total = scores.Sum(score => score.Score) }));
```

The [fan-out example](https://damikun.github.io/inngest-dotnet/examples/fan-out-with-invoke.md) builds on this. For a long list,
[`MapInChunksAsync`](https://damikun.github.io/inngest-dotnet/guides/fan-out.md#lists-too-long-for-one-run) invokes one function per chunk of
items and gathers the results in order.

## When the invoked function fails

If the invoked function fails all its attempts, or the timeout passes first, the step fails at once without retries
of its own, and `InvokeAsync` throws `StepFailedException`. Retrying the step would run the whole invoked function
again, and in a chain of invocations the retries would multiply. Catch the exception to carry on without that
result:

```csharp
AccountScore? score;
try
{
    score = await context.Step.InvokeAsync<ScoreAccount, AccountScore>("score-acme",
        new ScoreRequest("acme"), timeout: TimeSpan.FromMinutes(5));
}
catch (StepFailedException exception)
{
    context.Logger.LogWarning("Scoring failed: {Error}", exception.Error.Message);
    score = null;
}
```

Left uncaught, `StepFailedException` fails the run without retrying it. If the invoked function's rate limit skips
the run, the step fails in the same way.

## Passing the user on

An invoked function does not receive the user who caused the calling run by itself, because invocations do not pass
through middleware. With [`Inngest.UserContext`](https://damikun.github.io/inngest-dotnet/guides/user-context.md#pass-the-user-on), `WithUser` adds
the run's user to the options.

## Invoke or send an event?

Invoke when the caller needs the result or must know that the work finished. Send an event with
[`SendEventAsync`](https://damikun.github.io/inngest-dotnet/guides/sending-events.md) when the caller should not wait, or when any number of
functions may react to what happened.
