# Primitives · gotick docs

> Six of them. They all begin the same way — the flow context, then a key that identifies the step — and you steer between them with ordinary if and for.

Documentation

- [01Install and run](/en/docs.md)
- [02How it works](/en/docs/model.md)
- [03What's underneath](/en/docs/internals.md)
- [04Primitives](/en/docs/primitives.md)
- [05Driving from outside](/en/docs/control.md)
- [06Examples](/en/docs/examples.md)
- [07Running it](/en/docs/operations.md)
- [08Limits](/en/docs/limits.md)

# The primitives

Six of them. They all begin the same way — the flow context, then a key that identifies the step — and you steer between them with ordinary if and for.

On this page

- [Task](#task)
- [Memo](#memo)
- [Sleep](#sleep)
- [WaitForSignal](#signal)
- [Array](#array)
- [Async + Wait](#async)
- [Sequence](#sequence)
- [When a flow ends](#callbacks)

## Task

`func Task(c *Context, key string, fun TaskFun, opts ...TaskOption)`

Run a step. Return an error and it is retried with backoff; run out of retries and the whole flow fails. WithMaxRetry(n) sets the number of retries, not attempts, so at most n+1 executions — the default is 3, and WithMaxRetry(0) means one shot. Return gotick.AbortError to stop the flow deliberately instead of failing it. A step that has already failed aborts the flow on the next round rather than being skipped, so a broken step can never let the steps behind it run.

Copy

```
gotick.Task(ctx, "charge", func(c *gotick.TaskContext) error {    return billing.Charge(c, orderId) // c is a context.Context}, gotick.WithMaxRetry(5))
```

## Memo

`func Memo[T interface{}](ctx *Context, key string, build func() (T, error), opts ...TaskOption) T`

Run a step that produces a value, and remember the value. Later runs get it back without calling your builder again. This is how a database read, a generated id or a timestamp gets into a flow safely — the flow body may read the result freely, because every run reads the same one.

Copy

```
user := gotick.Memo(ctx, "user", func() (User, error) {    return db.GetUser(userId)}) // safe to branch on: every run reads the same valueif user.Plan == "pro" {    gotick.Task(ctx, "notify-csm", notifyCSM)}
```

## Sleep

`func Sleep(c *Context, key string, duration time.Duration)`

Wait, holding no process. The state is written down and an event is scheduled for later, so nothing of yours is running in the meantime and a restart in the middle changes nothing. Wake-up precision is DelayedTaskCheckInterval, 500ms by default.

Copy

```
gotick.Sleep(ctx, "wait-payment", 30*time.Minute)
```

## WaitForSignal

`func WaitForSignal[T any](c *Context, key string, opts ...SignalOption) (T, bool)`

Wait for something outside to happen. The bool tells you whether a signal arrived, so false means the timeout won. Without WithSignalTimeout it waits forever and no wake-up event is scheduled at all — only Cancel gets it out. The first signal wins and later ones are rejected, which is what makes every run read the same value; a signal that arrives before the flow even reaches the wait is kept, not lost.

Copy

```
paid, ok := gotick.WaitForSignal[Payment](ctx, "paid",    gotick.WithSignalTimeout(30*time.Minute)) if !ok {    gotick.Task(ctx, "close-order", closeOrder)    return}gotick.Task(ctx, "ship", func(c *gotick.TaskContext) error {    return shipping.Send(c, paid.TradeNo)})
```

## Array

`func Array[T interface{}](ctx *Context, key string, build func(ctx *TaskContext) ([]T, error), opts ...TaskOption) []ArrayWrap[T]`

Memo for a list. Use it whenever a later step loops over the result, so the loop has the same length and the same items on every run. Value() reads an element and Key(prefix) gives that element its own step key.

Copy

```
files := gotick.Array(ctx, "list", func(c *gotick.TaskContext) ([]string, error) {    return storage.List(c, dir)}) for _, f := range files {    name := f.Value()    gotick.Task(ctx, f.Key("convert"), func(c *gotick.TaskContext) error {        return convert(c, name)    })}
```

## Async + Wait

`func Async[T interface{}](ctx *Context, key string, f func(ctx *TaskContext) (T, error), opts ...TaskOption) *FutureT[T]func AsyncArray[T, A interface{}](ctx *Context, key string, arr []ArrayWrap[A], f func(ctx *TaskContext, a A, index int) (T, error), opts ...TaskOption) []Futurefunc Wait(ctx *Context, parallel int, fs ...Future)`

Declare steps that may run at the same time, then wait for them with a concurrency limit. AsyncArray is the shorthand for fanning out over an Array. Read a result with Value() once Wait has returned. WithMaxRetry works here too; the only difference is the default when you omit it — 5 retries for async steps versus 3 for a Task.

One wart to know about: AsyncArray returns \[\]Future, and Future does not expose Value(), so reading a result back needs a type assertion to the concrete future type. Async on its own returns \*FutureT\[T\] and needs no assertion.

Copy

```
fs := gotick.AsyncArray(ctx, "download", files,    func(c *gotick.TaskContext, url string, i int) (string, error) {        return download(c, url)    }) gotick.Wait(ctx, 4, fs...) // at most 4 at a time gotick.Task(ctx, "save", func(c *gotick.TaskContext) error {    for _, f := range fs {        path := f.(*gotick.FutureT[string]).Value()        _ = path    }    return nil})
```

## Sequence

`func Sequence(ctx *Context, key string, maxLen int) SequenceWrap`

A resumable counter, for a loop whose length isn't a list. Next() advances it and persists the position, and TaskKey(prefix) gives the current iteration a distinct step key. A negative maxLen loops until you break out yourself.

Copy

```
seq := gotick.Sequence(ctx, "pages", 100) for seq.Next() {    page := seq.Current    gotick.Task(ctx, seq.TaskKey("fetch"), func(c *gotick.TaskContext) error {        return fetchPage(c, page)    })}
```

## When a flow ends

Three callbacks hang off Flow, and they are ordinary steps: OnSuccess runs once the function has returned normally, OnFail runs when the flow has given up on a step, and OnError runs on each individual step failure — including the ones that will still be retried. Use OnError for alerting and OnFail for compensation.

Copy

```
tick.Flow("order/close", closeOrderFlow).    OnSuccess(func(ctx *gotick.Context) error {        return metrics.Inc("order.closed")    }).    OnError(func(ctx *gotick.Context, ts gotick.TaskStatus) error {        return alert.Warn(ctx.CallId, ts.Errs) // fires on every attempt    }).    OnFail(func(ctx *gotick.Context, ts gotick.TaskStatus) error {        return compensate(ctx.CallId) // fires once, after giving up    })
```

[← Previous\
\
What's underneath](/en/docs/internals.md) [Next →\
\
Driving from outside](/en/docs/control.md)

On this page

- [Task](#task)
- [Memo](#memo)
- [Sleep](#sleep)
- [WaitForSignal](#signal)
- [Array](#array)
- [Async + Wait](#async)
- [Sequence](#sequence)
- [When a flow ends](#callbacks)

> Full page index: [/llms.txt](/llms.txt)
