> For the complete documentation index, see [llms.txt](https://docs.skail.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.skail.dev/construa/como-fazer/esperar-um-evento-com-prazo.md).

# Esperar um evento com prazo

Toda espera por evento externo leva um prazo.

Este guia mostra o padrão de espera com prazo (esperar o evento ou o delay, o que vier primeiro), o que fazer quando o prazo vence, e como enviar um lembrete antes de vencer.

## O padrão

{% tabs %}
{% tab title="C#" %}

```csharp
[SkailFunction]
public async SkailTask AguardarAprovacao(Guid solicitacaoId)
{
    await NotificarAprovador(solicitacaoId);                                  // command

    var aprovacao = SkailTask.WaitForEvent<Decisao>(Eventos.SolicitacaoDecidida, solicitacaoId.ToString());
    var prazo     = SkailTask.Delay(TimeSpan.FromDays(3));

    var vencedor = await SkailTask.WhenAny(aprovacao, prazo);

    if (vencedor == prazo)
    {
        await RegistrarExpiracao(solicitacaoId);                              // command
        return;
    }

    var decisao = await aprovacao;                                            // já concluída; devolve o payload
    if (decisao.Aprovada) await Executar(solicitacaoId);
    else                  await RegistrarRecusa(solicitacaoId, decisao.Motivo);
}
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
import { Skail, SkailCommand, SkailFunction } from "@skail/runtime";

export const Eventos = {
  SolicitacaoDecidida: "SOLICITACAO_DECIDIDA",
} as const;

export interface Decisao {
  Aprovada: boolean;
  Motivo: string;
}

export class AprovacaoService {
  @SkailFunction()
  async AguardarAprovacao(solicitacaoId: string): Promise<void> {
    await this.NotificarAprovador(solicitacaoId);                             // command

    const aprovacao = Skail.waitForEvent<Decisao>(Eventos.SolicitacaoDecidida, solicitacaoId);
    const prazo     = Skail.delayMinutes(3 * 24 * 60);

    const decisao = await Skail.whenAny(aprovacao, prazo);

    if (decisao === undefined) {
      await this.RegistrarExpiracao(solicitacaoId);                           // command
      return;
    }

    if (decisao.Aprovada) await this.Executar(solicitacaoId);
    else                  await this.RegistrarRecusa(solicitacaoId, decisao.Motivo);
  }

  @SkailCommand() async NotificarAprovador(solicitacaoId: string): Promise<void> {}
  @SkailCommand() async RegistrarExpiracao(solicitacaoId: string): Promise<void> {}
  @SkailCommand() async Executar(solicitacaoId: string): Promise<void> {}
  @SkailCommand() async RegistrarRecusa(solicitacaoId: string, motivo: string): Promise<void> {}
}
```

{% endtab %}
{% endtabs %}

O código acima depende de quatro detalhes:

1. A espera pelo evento e o delay são criados sem `await` e passados juntos à primitiva que espera o primeiro que terminar; é ela que espera.
2. O retorno dela diz se foi timeout: em C#, ela devolve a task que concluiu primeiro, e comparar com `prazo` responde; em TypeScript, ela devolve o valor de quem terminou primeiro, e um prazo vencido aparece como `undefined`.
3. O payload sai sem esperar de novo: em C#, com `await` na espera que terminou primeiro; em TypeScript, ele já é o valor devolvido.
4. O `instanceId` é o id da solicitação, o mesmo que o fire vai usar.

{% hint style="info" %}
O que acontece com a espera que não terminou primeiro: se o evento chegar depois do prazo, ele não acorda nada nesta execução, porque a function já seguiu. O fire responde 2xx do mesmo jeito e o evento fica guardado até alguma execução esperar o mesmo nome e id, então a resposta não avisa quem dispara que o prazo já venceu. Para reagir a uma decisão atrasada, quem dispara consulta o estado da solicitação no próprio banco (gravado por `RegistrarExpiracao`) e, se o prazo venceu, inicia outra execução. O evento guardado atende a primeira espera com o mesmo nome e id, inclusive a da execução nova; se ela vai pedir uma decisão nova, use outro `instanceId` na espera e no fire (por exemplo, `{solicitacaoId}-2`). E não conte com o evento guardado por tempo indeterminado (ver [Garantias de execução](/referencia/garantias/garantias-de-execucao.md)).
{% endhint %}

## Decidir o que fazer no timeout

Depende do negócio, e a decisão é código na function:

* cancelar (o exemplo acima);
* aprovar por padrão (quando o prazo vence, a function segue como se a solicitação tivesse sido aprovada);
* escalar para outra pessoa (notifica e espera de novo, com novo prazo);
* marcar como pendente para intervenção manual.

Nenhuma dessas opções exige nada além de commands e outra espera com prazo.

## Lembrete antes do prazo

Use dois prazos em sequência: um curto para o lembrete e outro com o tempo restante para a decisão.

{% tabs %}
{% tab title="C#" %}

```csharp
var aprovacao = SkailTask.WaitForEvent<Decisao>(Eventos.SolicitacaoDecidida, solicitacaoId.ToString());

var lembrete = SkailTask.Delay(TimeSpan.FromDays(2));
if (await SkailTask.WhenAny(aprovacao, lembrete) == lembrete)
{
    await EnviarLembrete(solicitacaoId);                                      // command

    var restante = SkailTask.Delay(TimeSpan.FromDays(1));
    if (await SkailTask.WhenAny(aprovacao, restante) == restante)
    {
        await RegistrarExpiracao(solicitacaoId);
        return;
    }
}

var decisao = await aprovacao;
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
import { Skail, SkailCommand, SkailFunction } from "@skail/runtime";

export const Eventos = {
  SolicitacaoDecidida: "SOLICITACAO_DECIDIDA",
} as const;

export interface Decisao {
  Aprovada: boolean;
  Motivo: string;
}

export class AprovacaoComLembreteService {
  @SkailFunction()
  async AguardarAprovacao(solicitacaoId: string): Promise<void> {
    const aprovacao = Skail.waitForEvent<Decisao>(Eventos.SolicitacaoDecidida, solicitacaoId);

    const lembrete = Skail.delayMinutes(2 * 24 * 60);
    let decisao = await Skail.whenAny(aprovacao, lembrete);
    if (decisao === undefined) {
      await this.EnviarLembrete(solicitacaoId);                               // command

      const restante = Skail.delayMinutes(1 * 24 * 60);
      decisao = await Skail.whenAny(aprovacao, restante);
      if (decisao === undefined) {
        await this.RegistrarExpiracao(solicitacaoId);
        return;
      }
    }

    if (decisao.Aprovada) await this.Executar(solicitacaoId);
  }

  @SkailCommand() async EnviarLembrete(solicitacaoId: string): Promise<void> {}
  @SkailCommand() async RegistrarExpiracao(solicitacaoId: string): Promise<void> {}
  @SkailCommand() async Executar(solicitacaoId: string): Promise<void> {}
}
```

{% endtab %}
{% endtabs %}

A mesma `aprovacao` participa das duas esperas com prazo; ela continua pendente até ser atendida ou até a function terminar. O padrão completo, com várias mensagens, está em [Régua de lembretes](/construa/como-fazer/padroes-e-antipadroes/regua-de-lembretes.md).

## Prazo calculado a partir de uma data

Se o prazo é uma data de negócio (o vencimento da fatura), leia o relógio em um command e calcule o intervalo:

{% tabs %}
{% tab title="C#" %}

```csharp
var agora = await ObterDataAtual();                                           // command que devolve DateTime.UtcNow
var ateOVencimento = fatura.Vencimento - agora;
var prazo = SkailTask.Delay(ateOVencimento > TimeSpan.Zero ? ateOVencimento : TimeSpan.Zero);
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
import { Skail, SkailCommand, SkailFunction } from "@skail/runtime";

export interface Fatura {
  Id: string;
  Vencimento: string;
}

export class VencimentoService {
  @SkailFunction()
  async AguardarVencimento(fatura: Fatura): Promise<void> {
    const agora = await this.ObterDataAtual(); // command que devolve a data atual
    const ateOVencimentoMs = Date.parse(fatura.Vencimento) - Date.parse(agora);
    const prazo = Skail.delay(ateOVencimentoMs > 0 ? ateOVencimentoMs : 0);

    await prazo;
    await this.CobrarFatura(fatura.Id);
  }

  @SkailCommand()
  async ObterDataAtual(): Promise<string> {
    return new Date().toISOString();
  }

  @SkailCommand()
  async CobrarFatura(faturaId: string): Promise<void> {}
}
```

{% endtab %}
{% endtabs %}

{% hint style="warning" %}
Não leia o relógio direto na function: em C#, `DateTime.UtcNow` compila, mas quebra o replay; em TypeScript, `new Date()` e `Date.now()` lançam erro na hora. Ver [Tempo e agendamento](/construa/conceitos/tempo-e-agendamento.md).
{% endhint %}

## Erros comuns

| Sintoma                                                                                    | Causa                                                                  | Correção                                                             |
| ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- | -------------------------------------------------------------------- |
| (C#) `SkailNonDeterministicException` na retomada                                          | Prazo calculado com `DateTime.UtcNow` na function                      | Relógio via command                                                  |
| (TypeScript) Erro "... is not available in a deterministic Skail function. Use a command." | Prazo calculado com `new Date()` ou `Date.now()` na function           | Relógio via command                                                  |
| Timeout nunca dispara                                                                      | `await` direto na espera pelo evento, sem passar pela espera com prazo | Crie a espera e o delay sem `await` e espere o primeiro que terminar |
| Execução fica aguardando para sempre                                                       | Sem prazo                                                              | Este guia                                                            |

## Próximos passos

[Timeout e prazo](/construa/como-fazer/padroes-e-antipadroes/timeout-e-prazo.md) para as variações do padrão. [Human-in-the-loop](/construa/como-fazer/padroes-e-antipadroes/human-in-the-loop.md) para aprovações. A referência de [WhenAll e WhenAny](/referencia/sdk-csharp/whenall-e-whenany.md) e de [WaitForEvent](/referencia/sdk-csharp/waitforevent.md) no SDK C#.
