Invoking functions from other 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.
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
Section titled “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:
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);}[InngestFunction("score-account")]The attribute goes on ScoreAccount, and inngest.AddFunction<ScoreAccount>() registers it.
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.
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:
var score = await context.Step.InvokeAsync<AccountScore>("score-acme", new InvokeOptions("score-account") { Data = new { accountId = "acme" }, Timeout = TimeSpan.FromMinutes(5) });Options
Section titled “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 in the Inngest docs for its default.
Fan-out with results
Section titled “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:
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 builds on this. For a long list,
MapInChunksAsync invokes one function per chunk of
items and gathers the results in order.
When the invoked function fails
Section titled “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:
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
Section titled “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, WithUser adds
the run’s user to the options.
Invoke or send an event?
Section titled “Invoke or send an event?”Invoke when the caller needs the result or must know that the work finished. Send an event with
SendEventAsync when the caller should not wait, or when any number of
functions may react to what happened.
inngest-dotnet is a community Inngest SDK for .NET. It is not affiliated with or endorsed by Inngest Inc.