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 ¶
- func CloseIt[T io.Closer](_ context.Context, c T) error
- func Task[T any](_ context.Context, _ T) error
- type Action
- func (a Action[T]) AlterError(f func(error) error) Action[T]
- func (a Action[T]) Apply(v T) task.Task
- func (a Action[T]) Defer(f func()) Action[T]
- func (a Action[T]) NoCtx() func(T) error
- func (a Action[T]) NoErr() func(T)
- func (a Action[T]) Post(f func(T, error)) Action[T]
- func (a Action[T]) Pre(f func(v T)) Action[T]
- func (act Action[T]) Then(next Action[T]) Action[T]
- func (a Action[T]) Use(d Data[T]) task.Task
- func (a Action[T]) With(mod task.CtxMod) Action[T]
- type Action2
- type Action3
- type Action4
- type Converter
- func (c Converter[I, O]) AlterError(f func(error) error) Converter[I, O]
- func (c Converter[I, O]) AlterOutput(f func(O, error) (O, error)) Converter[I, O]
- func (c Converter[I, O]) By(i I) Data[O]
- func (c Converter[I, O]) Defer(f func()) Converter[I, O]
- func (c Converter[I, O]) From(i Data[I]) Data[O]
- func (c Converter[I, O]) Post(f func(I, O, error)) Converter[I, O]
- func (c Converter[I, O]) Pre(f func(I)) Converter[I, O]
- func (c Converter[I, O]) Then(next Converter[O, O]) Converter[I, O]
- func (c Converter[I, O]) With(mod task.CtxMod) Converter[I, O]
- type Converter2
- type Converter3
- type Converter4
- type Data
- func Future[T any]() (ret Data[T], determine func(T, error))
- func NoCtxUse[T any](f func() (T, error)) Data[T]
- func NoErrUse[T any](f func() T) Data[T]
- func Promise[T any]() (ret Data[T], resolve func(T), reject func(error))
- func Use[T any](f func(context.Context) (T, error)) Data[T]
- func UseError[T any](err error) Data[T]
- func UseValue[T any](v T) Data[T]
- func (d Data[T]) AlterError(f func(error) error) Data[T]
- func (d Data[T]) AlterOutput(f func(T, error) (T, error)) Data[T]
- func (d Data[T]) Cached() Data[T]
- func (d Data[T]) Default(v T) Data[T]
- func (d Data[T]) DefaultIf(errf func(error) bool, v T) Data[T]
- func (d Data[T]) DefaultIfNot(errf func(error) bool, v T) Data[T]
- func (d Data[T]) Defer(f func()) Data[T]
- func (d Data[T]) Do(a Action[T]) task.Task
- func (d Data[T]) Get(ctx context.Context) (T, error)
- func (d Data[T]) NoCtx() func() (T, error)
- func (d Data[T]) NoErr() func() T
- func (d Data[T]) Post(f func(T, error)) Data[T]
- func (d Data[T]) Pre(f func()) Data[T]
- func (d Data[T]) Retry() Data[T]
- func (d Data[T]) RetryIf(errf func(error) bool) Data[T]
- func (d Data[T]) RetryN(n int) Data[T]
- func (d Data[T]) RetryNIf(n int, errf func(error) bool) Data[T]
- func (d Data[T]) Saved() Data[T]
- func (d Data[T]) Then(c Converter[T, T]) Data[T]
- func (d Data[T]) Timed(dur time.Duration) Data[T]
- func (d Data[T]) TimedDone(dur time.Duration) Data[T]
- func (d Data[T]) TimedDoneF(f func(time.Duration) time.Duration) Data[T]
- func (d Data[T]) TimedF(f func(time.Duration) time.Duration) Data[T]
- func (d Data[T]) TimedFail(dur time.Duration) Data[T]
- func (d Data[T]) TimedFailF(f func(time.Duration) time.Duration) Data[T]
- func (d Data[T]) With(mod task.CtxMod) Data[T]
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Action ¶
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 (Action[T]) AlterError ¶
AlterError wraps a to convert error before return it.
func (Action[T]) NoErr ¶
func (a Action[T]) NoErr() func(T)
NoErr converts Action into simple function that ignores context and error.
type Action2 ¶
Action2 is an action that accepts two parameters.
type Action3 ¶
Action3 is like Action2, but accepts one more param.
type Action4 ¶
Action4 is like Action3, but accepts one more param.
type Converter ¶
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 Join ¶
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 (Converter[I, O]) AlterError ¶
AlterError creates a new Converter by modifying the error with f.
func (Converter[I, O]) AlterOutput ¶
AlterOutput creates a new Converter by modifying the output with f.
type Converter2 ¶
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.
type Converter3 ¶
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 ¶
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 ¶
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 ¶
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 (Data[T]) AlterError ¶
AlterError wraps d to run f on its error.
func (Data[T]) AlterOutput ¶
AlterOutput wraps d to run f on its output.
func (Data[T]) Cached ¶
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]) DefaultIf ¶
DefaultIf wraps d to return v instead if any error matched by errf occurred.
func (Data[T]) DefaultIfNot ¶
DefaultIfNot uses v if error occurred and is NOT matched by errf.
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]) RetryIf ¶ added in v0.3.4
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 ¶
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
RetryNIf is like RetryIf, but no more than n times.
Error passed to errf will never be nil.
func (Data[T]) Saved ¶
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]) Timed ¶
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]) TimedDoneF ¶
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 ¶
TimedF is like Timed, but use function instead.
The function accepts actual execution time, and returns how long it should wait.
func (Data[T]) TimedFailF ¶
TimedFailF is like TimedFail, but use function instead.
The function accepts actual execution time, and returns how long it should wait.