action

package
v0.3.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 27, 2025 License: MPL-2.0 Imports: 6 Imported by: 0

Documentation

Overview

Package action is designed to write code like this:

func fetchAPI(context.Context) (*http.Response, error)
func parseResult(context.Context, *http.Response) (MyDataType, error) {}
func saveToDB(context.Context, sql.DB, MyDataType) error {}
func generateReport(context.Context, MyDataType) error {}
someData := action.Get(parseResult).
	From(fetchAPI).
	RetryN(3).
	Cached()
err := action.Do2(saveToDB).
	Apply(dbConn).
	Then(generateReport).
	Use(someData).
	Run(ctx)

This approach eliminates most error processing code in current scope. Take a look at package example.

Performance consideration

It has small overhead since it wraps plain function. It is recommended to use this package only to codes doing IO operation or something that can be canceled, as the overhead is small enough to be ignored comparing to those operations.

Example
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

package main

import (
	"context"
	"database/sql"
	"errors"
	"os"
	"os/signal"
)

// read value from remote api, can be canceled by context.
func readData(ctx context.Context) (int, error) {
	// should add logging and error processing here
	return 1, nil
}

// create db connection.
func connectDB(dsn string) (*sql.DB, error) {
	// should add logging and error processing here
	_ = dsn
	return nil, errors.New("fake")
}

// close db
func closeDB(db *sql.DB) error {
	// should add logging and error processing here
	return db.Close()
}

// functions accepting [Data] is not recommended as it introduces extra complexity,
// but performance is barely improved.
func fasterCloseDB(db Data[*sql.DB]) func() {
	return func() {
		if conn, err := db(context.TODO()); err == nil {
			conn.Close()
		}
	}
}

// save val to db, can be canceled by context.
func saveToDB(ctx context.Context, db *sql.DB, val int) error {
	// should add logging and error processing here
	_, _, _ = ctx, db, val
	return errors.New("fake")
}

func main() {
	const dsn = "my db dsn"
	ctx, stop := signal.NotifyContext(context.TODO(), os.Interrupt)
	defer stop()

	// Traditional approach
	func(ctx context.Context) {
		db, err := connectDB(dsn)
		if err != nil {
			return
		}
		defer db.Close() // should add error processing

		data, err := readData(ctx)
		if err != nil {
			return
		}

		err = saveToDB(ctx, db, data)
		if err != nil {
			return
		}

		// what if you want to retry db connecting if failed?
	}(ctx)

	// Using this package, shared func should be renamed to be more descriptive
	//
	// Pros:
	//     - less if err != nil block
	//     - descriptive
	//     - easier to write testable code
	//     - useful helper to do common jobs like retrying
	//
	// Cons:
	//     - hard to trace
	//     - slower (can be ignored when doing IO task)
	//     - use more resource sometimes
	//     - need to cache the result sometimes, which uses extra sync.Once
	func(ctx context.Context) {
		db := NoCtxGet(connectDB).By(dsn).Cached()
		// to retry connecting, use this line instead
		// db := NoCtxGet(connectDB).By(dsn).RetryN(3).Cached()

		err := Do2(saveToDB).
			Use(db).
			Use(readData).
			Defer(NoCtxDo(closeDB).Use(db).NoErr()).
			// can be a little bit faster, but not worthing it
			// Defer(fasterCloseDB(db)).
			Run(ctx)
		if err != nil {
			return
		}
	}(ctx)
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func CloseIt

func CloseIt[T io.Closer](_ context.Context, c T) error

CloseIt is an Action that can close any closable Data.

func Task

func Task[T any](_ context.Context, _ T) error

Task is a predefined Action used to convert Data into task.Task like

err := Get(saveToDB).By(dbConn).From(myData).To(Task).Run(ctx)

Types

type Action

type Action[T any] func(context.Context, T) error

Action is a specialized task.Task which accepts one param.

It is designed to interact with the value generated by Data to do something. An task.Task is created after you bind action and data together. It is suggested to name it using short phrase to describe what will it do, like "saveToDB", to write code like this:

Use(resp).To(saveToDB)
Do(saveToDB).Use(resp)

Both means if value is generated by resp successfully, save it to DB and return any error.

func Do

func Do[T any](f func(context.Context, T) error) Action[T]

Do wraps f into Action, mostly for for typing purpose.

func NoCtxDo

func NoCtxDo[T any](f func(T) error) Action[T]

NoCtxDo creates Action from non-cancellable function.

func NoErrDo

func NoErrDo[T any](f func(T)) Action[T]

NoErrDo creates Action from non-cancellable, never-fail function.

func (Action[T]) AlterError

func (a Action[T]) AlterError(f func(error) error) Action[T]

AlterError wraps a to convert error before return it.

func (Action[T]) Apply

func (a Action[T]) Apply(v T) task.Task

Apply creates a task.Task that executes the action with a value.

func (Action[T]) Defer

func (a Action[T]) Defer(f func()) Action[T]

Defer wraps a to run f after it.

func (Action[T]) NoCtx

func (a Action[T]) NoCtx() func(T) error

NoCtx converts Action into simple function that ignores the context.

func (Action[T]) NoErr

func (a Action[T]) NoErr() func(T)

NoErr converts Action into simple function that ignores context and error.

func (Action[T]) Post

func (a Action[T]) Post(f func(T, error)) Action[T]

Post wraps a to run f after it.

func (Action[T]) Pre

func (a Action[T]) Pre(f func(v T)) Action[T]

Pre wraps a to run f before it.

func (Action[T]) Then

func (act Action[T]) Then(next Action[T]) Action[T]

Then creates an Action which runs next after act is finished successfully.

func (Action[T]) Use

func (a Action[T]) Use(d Data[T]) task.Task

Use creates a task.Task that executes the action with Data. The value of data is generated on-the-fly.

func (Action[T]) With

func (a Action[T]) With(mod task.CtxMod) Action[T]

With wraps a to modify context before run it.

type Action2

type Action2[A, B any] func(context.Context, A, B) error

Action2 is an action that accepts two parameters.

func Do2

func Do2[A, B any](f func(context.Context, A, B) error) Action2[A, B]

Do2 creates an Action2, mostly for type converting purpose.

func NoCtxDo2

func NoCtxDo2[A, B any](f func(A, B) error) Action2[A, B]

NoCtxDo2 is like Do2, but the function is not cancellable.

func NoErrDo2

func NoErrDo2[A, B any](f func(A, B)) Action2[A, B]

NoErrDo2 is like Do2, but the function is not cancellable and never fail.

func (Action2[A, B]) Apply

func (act Action2[A, B]) Apply(va A) Action[B]

Apply creates an Action by currifying act with a raw value.

func (Action2[A, B]) Then

func (act Action2[A, B]) Then(next Action2[A, B]) Action2[A, B]

Then creates an Action2 by running next after act if finished successfully.

func (Action2[A, B]) Use

func (act Action2[A, B]) Use(a Data[A]) Action[B]

Use creates an Action by currifying act with Data.

type Action3

type Action3[A, B, C any] func(context.Context, A, B, C) error

Action3 is like Action2, but accepts one more param.

func Do3

func Do3[A, B, C any](f func(context.Context, A, B, C) error) Action3[A, B, C]

Do3 creates an Action3, mostly for type converting purpose.

func NoCtxDo3

func NoCtxDo3[A, B, C any](f func(A, B, C) error) Action3[A, B, C]

NoCtxDo3 is like Do3, but the function is not cancellable.

func NoErrDo3

func NoErrDo3[A, B, C any](f func(A, B, C)) Action3[A, B, C]

NoErrDo3 is like Do3, but the function is not cancellable and never fail.

func (Action3[A, B, C]) Apply

func (act Action3[A, B, C]) Apply(va A) Action2[B, C]

Apply creates an Action2 by currifying act with a raw value.

func (Action3[A, B, C]) Then

func (act Action3[A, B, C]) Then(next Action3[A, B, C]) Action3[A, B, C]

Then creates an Action3 by running next after act if finished successfully.

func (Action3[A, B, C]) Use

func (act Action3[A, B, C]) Use(a Data[A]) Action2[B, C]

Use creates an Action2 by currifying act with Data.

type Action4

type Action4[A, B, C, D any] func(context.Context, A, B, C, D) error

Action4 is like Action3, but accepts one more param.

func Do4

func Do4[A, B, C, D any](f func(context.Context, A, B, C, D) error) Action4[A, B, C, D]

Do4 creates an Action4, mostly for type converting purpose.

func NoCtxDo4

func NoCtxDo4[A, B, C, D any](f func(A, B, C, D) error) Action4[A, B, C, D]

NoCtxDo4 is like Do4, but the function is not cancellable.

func NoErrDo4

func NoErrDo4[A, B, C, D any](f func(A, B, C, D)) Action4[A, B, C, D]

NoErrDo4 is like Do4, but the function is not cancellable.

func (Action4[A, B, C, D]) Apply

func (act Action4[A, B, C, D]) Apply(va A) Action3[B, C, D]

Apply creates an Action3 by currifying act with a raw value.

func (Action4[A, B, C, D]) Then

func (act Action4[A, B, C, D]) Then(next Action4[A, B, C, D]) Action4[A, B, C, D]

Then creates an Action4 by running next after act if finished successfully.

func (Action4[A, B, C, D]) Use

func (act Action4[A, B, C, D]) Use(a Data[A]) Action3[B, C, D]

Use creates an Action3 by currifying act with Data.

type Converter

type Converter[I, O any] func(context.Context, I) (O, error)

Converter is a function to convert one type of data into another.

It is designed to interact with the value generated by Data to create a new value. In other words, it creates a new Data from one Data. It is suggested to name it using short phrase to describe what new data is, like "response", to write code like this:

Get(response).From(request)

which means creates a response by do something to request and returns any error.

func Get

func Get[I, O any](f func(context.Context, I) (O, error)) Converter[I, O]

Get creates a Converter, mostly for type converting.

func Join

func Join[A, B, C any](i Converter[A, B], j Converter[B, C]) Converter[A, C]

Join creates a new Converter by joining two converters.

It's impossible to implement something like Join(i, j, k, ...) because of language design.

func NoCtxGet

func NoCtxGet[I, O any](f func(I) (O, error)) Converter[I, O]

NoCtxGet creates Converter from f by ignoring context.

func NoErrGet

func NoErrGet[I, O any](f func(I) O) Converter[I, O]

NoErrGet creates Converter from f by ignoring context and error.

func (Converter[I, O]) AlterError

func (c Converter[I, O]) AlterError(f func(error) error) Converter[I, O]

AlterError creates a new Converter by modifying the error with f.

func (Converter[I, O]) AlterOutput

func (c Converter[I, O]) AlterOutput(f func(O, error) (O, error)) Converter[I, O]

AlterOutput creates a new Converter by modifying the output with f.

func (Converter[I, O]) By

func (c Converter[I, O]) By(i I) Data[O]

By is like From but uses raw value instead of Data.

func (Converter[I, O]) Defer

func (c Converter[I, O]) Defer(f func()) Converter[I, O]

Defer wraps c to run f after it.

func (Converter[I, O]) From

func (c Converter[I, O]) From(i Data[I]) Data[O]

From creates a Data of output type by feeding i to the converter.

func (Converter[I, O]) Post

func (c Converter[I, O]) Post(f func(I, O, error)) Converter[I, O]

Post wraps c to run f after it.

func (Converter[I, O]) Pre

func (c Converter[I, O]) Pre(f func(I)) Converter[I, O]

Pre wraps c to run f before it.

func (Converter[I, O]) Then

func (c Converter[I, O]) Then(next Converter[O, O]) Converter[I, O]

Then creates a new Converter by chaining two converters.

Type of next is limited to O -> O by language design.

func (Converter[I, O]) With

func (c Converter[I, O]) With(mod task.CtxMod) Converter[I, O]

With wraps c to modify the context before run it.

type Converter2

type Converter2[A, B, O any] func(context.Context, A, B) (O, error)

Converter2 is an Converter that accepts two input.

func Get2

func Get2[A, B, O any](f func(context.Context, A, B) (O, error)) Converter2[A, B, O]

Get2 creates an Converter2, mostly for type converting purpose.

func NoCtxGet2

func NoCtxGet2[A, B, O any](f func(A, B) (O, error)) Converter2[A, B, O]

NoCtxGet2 is "NoCtx" version of Get2.

func NoErrGet2

func NoErrGet2[A, B, O any](f func(A, B) O) Converter2[A, B, O]

NoErrGet2 is "NoErr" version of Get2.

func (Converter2[A, B, O]) By

func (c Converter2[A, B, O]) By(va A) Converter[B, O]

By creates a Converter by currifying c with a value.

func (Converter2[A, B, O]) From

func (c Converter2[A, B, O]) From(a Data[A]) Converter[B, O]

From creates a Converter by currifying c with a Data.

type Converter3

type Converter3[A, B, C, O any] func(context.Context, A, B, C) (O, error)

Converter3 is an Converter2 with additional input.

func Get3

func Get3[A, B, C, O any](f func(context.Context, A, B, C) (O, error)) Converter3[A, B, C, O]

Get3 creates an Converter3, mostly for type converting purpose.

func NoCtxGet3

func NoCtxGet3[A, B, C, O any](f func(A, B, C) (O, error)) Converter3[A, B, C, O]

NoCtxGet3 is "NoCtx" version of Get3.

func NoErrGet3

func NoErrGet3[A, B, C, O any](f func(A, B, C) O) Converter3[A, B, C, O]

NoErrGet3 is "NoErr" version of Get3.

func (Converter3[A, B, C, O]) By

func (c Converter3[A, B, C, O]) By(va A) Converter2[B, C, O]

By creates a Converter2 by currifying c with a value.

func (Converter3[A, B, C, O]) From

func (c Converter3[A, B, C, O]) From(a Data[A]) Converter2[B, C, O]

From creates a Converter2 by currifying c with a Data.

type Converter4

type Converter4[A, B, C, D, O any] func(context.Context, A, B, C, D) (O, error)

Converter4 is an Converter3 with additional input.

func Get4

func Get4[A, B, C, D, O any](f func(context.Context, A, B, C, D) (O, error)) Converter4[A, B, C, D, O]

Get4 creates an Converter4, mostly for type converting purpose.

func NoCtxGet4

func NoCtxGet4[A, B, C, D, O any](f func(A, B, C, D) (O, error)) Converter4[A, B, C, D, O]

NoCtxGet4 is "NoCtx" version of Get4.

func NoErrGet4

func NoErrGet4[A, B, C, D, O any](f func(A, B, C, D) O) Converter4[A, B, C, D, O]

NoErrGet4 is "NoErr" version of Get4.

func (Converter4[A, B, C, D, O]) By

func (c Converter4[A, B, C, D, O]) By(va A) Converter3[B, C, D, O]

By creates a Converter3 by currifying c with a value.

func (Converter4[A, B, C, D, O]) From

func (c Converter4[A, B, C, D, O]) From(a Data[A]) Converter3[B, C, D, O]

From creates a Converter3 by currifying c with a Data.

type Data

type Data[T any] func(context.Context) (T, error)

Data is a function which can generate some data.

Though it is named "data", it's more like a factory or "promise in JS". It is used to generate value to be used with Action or Converter. Take a look at their document for more info.

func Future

func Future[T any]() (ret Data[T], determine func(T, error))

Future creates a cached Data, whose result is determined by a function. Getting value of the Data will be blocked until the result is determined.

func NoCtxUse

func NoCtxUse[T any](f func() (T, error)) Data[T]

NoCtxUse converts f into Data, so context is ignored.

func NoErrUse

func NoErrUse[T any](f func() T) Data[T]

NoErrUse converts f into Data, so context is ignored and never fail.

func Promise

func Promise[T any]() (ret Data[T], resolve func(T), reject func(error))

Promise is identical to Future but provides different type of function.

func Use

func Use[T any](f func(context.Context) (T, error)) Data[T]

Use converts f to Data, for type conversion purpose.

func UseError

func UseError[T any](err error) Data[T]

UseError creates a Data that always fail.

func UseValue

func UseValue[T any](v T) Data[T]

UseValue creates a Data from fixed value.

func (Data[T]) AlterError

func (d Data[T]) AlterError(f func(error) error) Data[T]

AlterError wraps d to run f on its error.

func (Data[T]) AlterOutput

func (d Data[T]) AlterOutput(f func(T, error) (T, error)) Data[T]

AlterOutput wraps d to run f on its output.

func (Data[T]) Cached

func (d Data[T]) Cached() Data[T]

Cached wraps d to cache its result, no matter success or failed.

Example
a := NoCtxUse(func() (int32, error) {
	fmt.Println("executed")
	ret := rand.Int31n(100)
	if ret < 50 {
		return 0, errors.New("50/50")
	}
	return ret, nil
}).Cached()

v1, e1 := a(context.TODO()) // print "executed"
v2, e2 := a(context.TODO()) // nothing printed

if v1 != v2 {
	fmt.Println("v1 != v2")
}
if e1 != e2 {
	fmt.Println("e1 != e2")
}
Output:

executed

func (Data[T]) Default

func (d Data[T]) Default(v T) Data[T]

Default wraps d to provide default value whenever it failed.

func (Data[T]) DefaultIf

func (d Data[T]) DefaultIf(errf func(error) bool, v T) Data[T]

DefaultIf wraps d to return v instead if any error matched by errf occurred.

func (Data[T]) DefaultIfNot

func (d Data[T]) DefaultIfNot(errf func(error) bool, v T) Data[T]

DefaultIfNot uses v if error occurred and is NOT matched by errf.

func (Data[T]) Defer

func (d Data[T]) Defer(f func()) Data[T]

Defer wraps d to run f after it.

func (Data[T]) Do

func (d Data[T]) Do(a Action[T]) task.Task

Do creates a task.Task by doing something with its value.

func (Data[T]) Get

func (d Data[T]) Get(ctx context.Context) (T, error)

Get generates a value from Data.

func (Data[T]) NoCtx

func (d Data[T]) NoCtx() func() (T, error)

NoCtx converts Data into simple function that ignores context.

func (Data[T]) NoErr

func (d Data[T]) NoErr() func() T

NoErr converts Data into simple function that ignores context and error.

func (Data[T]) Post

func (d Data[T]) Post(f func(T, error)) Data[T]

Post wraps d to run f after it.

func (Data[T]) Pre

func (d Data[T]) Pre(f func()) Data[T]

Pre wraps d to run f before it.

func (Data[T]) Retry

func (d Data[T]) Retry() Data[T]

Retry wraps d to run it repeatly until success.

func (Data[T]) RetryIf added in v0.3.4

func (d Data[T]) RetryIf(errf func(error) bool) Data[T]

RetryIf wraps d to run it repeatly until success or errf returns false.

Error passed to errf will never be nil.

func (Data[T]) RetryN

func (d Data[T]) RetryN(n int) Data[T]

RetryN is like Retry, but no more than n times.

RetryN(3) will run at most 4 times, first attempt is not considered as retrying.

func (Data[T]) RetryNIf added in v0.3.4

func (d Data[T]) RetryNIf(n int, errf func(error) bool) Data[T]

RetryNIf is like RetryIf, but no more than n times.

Error passed to errf will never be nil.

func (Data[T]) Saved

func (d Data[T]) Saved() Data[T]

Saved wraps d to cache its result only when success.

Example
err := errors.New("error")
cnt := 0
a := NoCtxUse(func() (int, error) {
	fmt.Println("executed")
	cnt++
	if cnt < 2 {
		return cnt, err
	}
	return cnt, nil
}).Saved()

fmt.Println(a(context.TODO()))
fmt.Println(a(context.TODO()))
fmt.Println(a(context.TODO()))
Output:

executed
1 error
executed
2 <nil>
2 <nil>

func (Data[T]) Then

func (d Data[T]) Then(c Converter[T, T]) Data[T]

Then creates a new Data by converting d with c.

func (Data[T]) Timed

func (d Data[T]) Timed(dur time.Duration) Data[T]

Timed wraps d into a Data and ensures that it is not returned before dur passed.

It focuses on "How long I should wait before returning". Take a look at example of task.Task.Timed for how it works.

func (Data[T]) TimedDone

func (d Data[T]) TimedDone(dur time.Duration) Data[T]

TimedDone is like Timed, but only successful run is limited.

func (Data[T]) TimedDoneF

func (d Data[T]) TimedDoneF(f func(time.Duration) time.Duration) Data[T]

TimedDoneF is like TimedDone, but use function instead.

The function accepts actual execution time, and returns how long it should wait.

func (Data[T]) TimedF

func (d Data[T]) TimedF(f func(time.Duration) time.Duration) Data[T]

TimedF is like Timed, but use function instead.

The function accepts actual execution time, and returns how long it should wait.

func (Data[T]) TimedFail

func (d Data[T]) TimedFail(dur time.Duration) Data[T]

TimedFail is like Timed, but only failed run is limited.

func (Data[T]) TimedFailF

func (d Data[T]) TimedFailF(f func(time.Duration) time.Duration) Data[T]

TimedFailF is like TimedFail, but use function instead.

The function accepts actual execution time, and returns how long it should wait.

func (Data[T]) With

func (d Data[T]) With(mod task.CtxMod) Data[T]

With wraps d to adjust its context.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL