1
0
mirror of https://github.com/duke-git/lancet.git synced 2026-02-04 12:52:28 +08:00

refactor rename async package name to

This commit is contained in:
dudaodong
2023-03-24 11:43:56 +08:00
parent a41d461910
commit 1cd3be508c
3 changed files with 4 additions and 4 deletions

279
promise/promise.go Normal file
View File

@@ -0,0 +1,279 @@
// Copyright 2023 dudaodong@gmail.com. All rights reserved.
// Use of this source code is governed by MIT license
// Package promise contains some functions to support async programming.
package promise
import (
"errors"
"fmt"
"sync"
"github.com/duke-git/lancet/v2/internal"
)
// Promise represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
// ref : chebyrash/promise (https://github.com/chebyrash/promise)
// see js promise: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
type Promise[T any] struct {
runnable func(resolve func(T), reject func(error))
result T
err error
pending bool
mu *sync.Mutex
wg *sync.WaitGroup
}
// New create a new promise instance.
func New[T any](runnable func(resolve func(T), reject func(error))) *Promise[T] {
if runnable == nil {
panic("runnable function should not be nil")
}
p := &Promise[T]{
runnable: runnable,
pending: true,
mu: &sync.Mutex{},
wg: &sync.WaitGroup{},
}
defer p.run()
return p
}
func (p *Promise[T]) run() {
p.wg.Add(1)
go func() {
defer func() {
if !p.pending {
return
}
if err := recover(); err != nil {
p.reject(errors.New(fmt.Sprint(err)))
}
}()
p.runnable(p.resolve, p.reject)
}()
}
// Resolve returns a Promise that has been resolved with a given value.
func Resolve[T any](resolution T) *Promise[T] {
return &Promise[T]{
result: resolution,
pending: false,
mu: &sync.Mutex{},
wg: &sync.WaitGroup{},
}
}
func (p *Promise[T]) resolve(value T) {
p.mu.Lock()
defer p.mu.Unlock()
if !p.pending {
return
}
p.result = value
p.pending = false
p.wg.Done()
}
// Reject returns a Promise that has been rejected with a given error.
func Reject[T any](err error) *Promise[T] {
return &Promise[T]{
err: err,
pending: false,
mu: &sync.Mutex{},
wg: &sync.WaitGroup{},
}
}
func (p *Promise[T]) reject(err error) {
p.mu.Lock()
defer p.mu.Unlock()
if !p.pending {
return
}
p.err = err
p.pending = false
p.wg.Done()
}
// Then allows chain calls to other promise methods.
func Then[T1, T2 any](promise *Promise[T1], resolve1 func(value T1) T2) *Promise[T2] {
return New(func(resolve2 func(T2), reject func(error)) {
result, err := promise.Await()
if err != nil {
reject(err)
return
}
resolve2(resolve1(result))
})
}
// Then allows chain calls to other promise methods.
func (p *Promise[T]) Then(resolve func(value T) T) *Promise[T] {
return New(func(_resolve func(T), reject func(error)) {
result, err := p.Await()
if err != nil {
reject(err)
return
}
_resolve(resolve(result))
})
}
// Catch allows to chain promises.
func Catch[T any](promise *Promise[T], rejection func(err error) error) *Promise[T] {
return New(func(resolve func(T), reject func(error)) {
result, err := promise.Await()
if err != nil {
reject(rejection(err))
return
}
resolve(result)
})
}
// Catch chain an existing promise with an intermediate reject function.
func (p *Promise[T]) Catch(reject func(error) error) *Promise[T] {
return New(func(resolve func(T), rej func(error)) {
resutl, err := p.Await()
if err != nil {
rej(reject(err))
return
}
resolve(resutl)
})
}
// Await blocks until the 'runable' to finish execution.
func (p *Promise[T]) Await() (T, error) {
p.wg.Wait()
return p.result, p.err
}
type tuple[T1, T2 any] struct {
_1 T1
_2 T2
}
// All resolves when all of the promises have resolved, reject immediately upon any of the input promises rejecting.
func All[T any](promises []*Promise[T]) *Promise[[]T] {
if len(promises) == 0 {
return nil
}
return New(func(resolve func([]T), reject func(error)) {
valsChan := make(chan tuple[T, int], len(promises))
errsChan := make(chan error, 1)
for idx, p := range promises {
idx := idx
_ = Then(p, func(data T) T {
valsChan <- tuple[T, int]{_1: data, _2: idx}
return data
})
_ = Catch(p, func(err error) error {
errsChan <- err
return err
})
}
resolutions := make([]T, len(promises))
for idx := 0; idx < len(promises); idx++ {
select {
case val := <-valsChan:
resolutions[val._2] = val._1
case err := <-errsChan:
reject(err)
return
}
}
resolve(resolutions)
})
}
// Race will settle the first fullfiled promise among muti promises.
func Race[T any](promises []*Promise[T]) *Promise[T] {
if len(promises) == 0 {
return nil
}
return New(func(resolve func(T), reject func(error)) {
valsChan := make(chan T, 1)
errsChan := make(chan error, 1)
for _, p := range promises {
_ = Then(p, func(data T) T {
valsChan <- data
return data
})
_ = Catch(p, func(err error) error {
errsChan <- err
return err
})
}
select {
case val := <-valsChan:
resolve(val)
case err := <-errsChan:
reject(err)
}
})
}
// Any resolves as soon as any of the input's Promises resolve, with the value of the resolved Promise.
// Any rejects if all of the given Promises are rejected with a combination of all errors.
func Any[T any](promises []*Promise[T]) *Promise[T] {
if len(promises) == 0 {
return nil
}
return New(func(resolve func(T), reject func(error)) {
valsChan := make(chan T, 1)
errsChan := make(chan tuple[error, int], len(promises))
for idx, p := range promises {
idx := idx
_ = Then(p, func(data T) T {
valsChan <- data
return data
})
_ = Catch(p, func(err error) error {
errsChan <- tuple[error, int]{_1: err, _2: idx}
return err
})
}
errs := make([]error, len(promises))
for idx := 0; idx < len(promises); idx++ {
select {
case val := <-valsChan:
resolve(val)
return
case err := <-errsChan:
errs[err._2] = err._1
}
}
errCombo := errs[0]
for _, err := range errs[1:] {
errCombo = internal.JoinError(err)
}
reject(errCombo)
})
}

View File

@@ -0,0 +1,195 @@
package promise
import (
"errors"
"fmt"
"time"
"github.com/duke-git/lancet/v2/internal"
)
func ExampleNew() {
p := New(func(resolve func(string), reject func(error)) {
resolve("hello")
})
val, err := p.Await()
if err != nil {
return
}
fmt.Println(val)
// Output:
// hello
}
func ExampleThen() {
p1 := New(func(resolve func(string), reject func(error)) {
resolve("hello ")
})
p2 := Then(p1, func(s string) string {
return s + "world"
})
result, err := p2.Await()
if err != nil {
return
}
fmt.Println(result)
// Output:
// hello world
}
func ExamplePromise_Then() {
p1 := New(func(resolve func(string), reject func(error)) {
resolve("hello ")
})
p2 := p1.Then(func(s string) string {
return s + "world"
})
result, err := p2.Await()
if err != nil {
return
}
fmt.Println(result)
// Output:
// hello world
}
func ExampleCatch() {
p1 := New(func(resolve func(string), reject func(error)) {
err := errors.New("error1")
reject(err)
})
p2 := Catch(p1, func(err error) error {
e := errors.New("error2")
return internal.JoinError(err, e)
})
_, err := p1.Await()
fmt.Println(err.Error())
result2, err := p2.Await()
fmt.Println(result2)
fmt.Println(err.Error())
// Output:
// error1
//
// error1
// error2
}
func ExamplePromise_Catch() {
p1 := New(func(resolve func(string), reject func(error)) {
err := errors.New("error1")
reject(err)
})
p2 := p1.Catch(func(err error) error {
e := errors.New("error2")
return internal.JoinError(err, e)
})
_, err := p1.Await()
fmt.Println(err.Error())
result2, err := p2.Await()
fmt.Println(result2)
fmt.Println(err.Error())
// Output:
// error1
//
// error1
// error2
}
func ExampleAll() {
p1 := New(func(resolve func(string), reject func(error)) {
resolve("a")
})
p2 := New(func(resolve func(string), reject func(error)) {
resolve("b")
})
p3 := New(func(resolve func(string), reject func(error)) {
resolve("c")
})
pms := []*Promise[string]{p1, p2, p3}
p := All(pms)
result, err := p.Await()
if err != nil {
return
}
fmt.Println(result)
// Output:
// [a b c]
}
func ExampleAny() {
p1 := New(func(resolve func(string), reject func(error)) {
time.Sleep(time.Millisecond * 250)
resolve("fast")
})
p2 := New(func(resolve func(string), reject func(error)) {
time.Sleep(time.Millisecond * 500)
resolve("slow")
})
p3 := New(func(resolve func(string), reject func(error)) {
reject(errors.New("error"))
})
pms := []*Promise[string]{p1, p2, p3}
p := Any(pms)
result, err := p.Await()
if err != nil {
return
}
fmt.Println(result)
// Output:
// fast
}
func ExampleRace() {
p1 := New(func(resolve func(string), reject func(error)) {
time.Sleep(time.Millisecond * 100)
resolve("fast")
})
p2 := New(func(resolve func(string), reject func(error)) {
time.Sleep(time.Millisecond * 300)
resolve("slow")
})
pms := []*Promise[string]{p1, p2}
p := Race(pms)
result, err := p.Await()
if err != nil {
return
}
fmt.Println(result)
// Output:
// fast
}

302
promise/promise_test.go Normal file
View File

@@ -0,0 +1,302 @@
package promise
import (
"errors"
"testing"
"time"
"github.com/duke-git/lancet/v2/internal"
)
func TestResolve(t *testing.T) {
assert := internal.NewAssert(t, "TestResolve")
p := Resolve("abc")
assert.Equal("abc", p.result)
assert.Equal(false, p.pending)
}
func TestReject(t *testing.T) {
assert := internal.NewAssert(t, "TestReject")
err := errors.New("error")
p := Reject[string](err)
assert.Equal("error", p.err.Error())
assert.Equal(false, p.pending)
}
func TestThen(t *testing.T) {
assert := internal.NewAssert(t, "TestThen")
p1 := New(func(resolve func(string), reject func(error)) {
resolve("abc")
})
p2 := Then(p1, func(data string) string {
return data + "de"
})
val, err := p1.Await()
assert.IsNil(err)
assert.Equal("abc", val)
val, err = p2.Await()
assert.IsNil(err)
assert.Equal("abcde", val)
}
func TestPromise_Then(t *testing.T) {
assert := internal.NewAssert(t, "TestPromise_Then")
p1 := New(func(resolve func(int), reject func(error)) {
resolve(1)
})
p2 := p1.Then(func(n int) int {
return n + 2
})
val, err := p1.Await()
assert.IsNil(err)
assert.Equal(1, val)
val, err = p2.Await()
assert.IsNil(err)
assert.Equal(3, val)
}
func TestCatch(t *testing.T) {
assert := internal.NewAssert(t, "TestCatch")
p1 := New(func(resolve func(string), reject func(error)) {
err := errors.New("error1")
reject(err)
})
p2 := Catch(p1, func(err error) error {
e := errors.New("error2")
return internal.JoinError(err, e)
})
val, err := p1.Await()
assert.Equal("", val)
assert.IsNotNil(err)
assert.Equal("error1", err.Error())
val, err = p2.Await()
assert.Equal("", val)
assert.IsNotNil(err)
assert.Equal("error1\nerror2", err.Error())
}
func TestPromise_Catch(t *testing.T) {
assert := internal.NewAssert(t, "TestPromise_Catch")
p1 := New(func(resolve func(string), reject func(error)) {
err := errors.New("error1")
reject(err)
})
p2 := p1.Catch(func(err error) error {
e := errors.New("error2")
return internal.JoinError(err, e)
})
val, err := p1.Await()
assert.Equal("", val)
assert.IsNotNil(err)
assert.Equal("error1", err.Error())
val, err = p2.Await()
assert.Equal("", val)
assert.IsNotNil(err)
assert.Equal("error1\nerror2", err.Error())
}
func TestAll(t *testing.T) {
assert := internal.NewAssert(t, "TestPromise_All")
t.Run("AllPromisesFullfilled", func(_ *testing.T) {
p1 := New(func(resolve func(string), reject func(error)) {
resolve("a")
})
p2 := New(func(resolve func(string), reject func(error)) {
resolve("b")
})
p3 := New(func(resolve func(string), reject func(error)) {
resolve("c")
})
p := All([]*Promise[string]{p1, p2, p3})
val, err := p.Await()
assert.Equal([]string{"a", "b", "c"}, val)
assert.IsNil(err)
})
t.Run("EmptyPromises", func(_ *testing.T) {
var empty = []*Promise[any]{}
p := All(empty)
assert.IsNil(p)
})
t.Run("PromisesContainRejected", func(_ *testing.T) {
p1 := New(func(resolve func(string), reject func(error)) {
resolve("a")
})
p2 := New(func(resolve func(string), reject func(error)) {
reject(errors.New("error1"))
})
p3 := New(func(resolve func(string), reject func(error)) {
reject(errors.New("error2"))
})
p := All([]*Promise[string]{p1, p2, p3})
_, err := p.Await()
assert.IsNotNil(err)
// assert.Equal("error1", err.Error())
})
t.Run("PromisesOnlyRejected", func(_ *testing.T) {
p1 := New(func(resolve func(string), reject func(error)) {
reject(errors.New("error1"))
})
p2 := New(func(resolve func(string), reject func(error)) {
reject(errors.New("error2"))
})
p3 := New(func(resolve func(string), reject func(error)) {
reject(errors.New("error3"))
})
p := All([]*Promise[string]{p1, p2, p3})
_, err := p.Await()
assert.IsNotNil(err)
// assert.Equal("error1", err.Error())
})
}
func TestAny(t *testing.T) {
assert := internal.NewAssert(t, "TestPromise_Any")
t.Run("AnyFullfilled", func(_ *testing.T) {
p1 := New(func(resolve func(string), reject func(error)) {
time.Sleep(time.Millisecond * 250)
resolve("fast")
})
p2 := New(func(resolve func(string), reject func(error)) {
time.Sleep(time.Millisecond * 500)
resolve("slow")
})
p3 := New(func(resolve func(string), reject func(error)) {
reject(errors.New("error"))
})
p := Any([]*Promise[string]{p1, p2, p3})
val, err := p.Await()
assert.Equal("fast", val)
assert.IsNil(err)
})
t.Run("EmptyPromises", func(_ *testing.T) {
var empty = []*Promise[any]{}
p := Any(empty)
assert.IsNil(p)
})
t.Run("OnlyRejected", func(_ *testing.T) {
p1 := New(func(resolve func(string), reject func(error)) {
reject(errors.New("error1"))
})
p2 := New(func(resolve func(string), reject func(error)) {
reject(errors.New("error2"))
})
p := Any([]*Promise[string]{p1, p2})
_, err := p.Await()
assert.IsNotNil(err)
// assert.Equal("error1", err.Error())
})
}
func TestRace(t *testing.T) {
assert := internal.NewAssert(t, "TestPromise_Race")
t.Run("PromisesFullfilled", func(_ *testing.T) {
p1 := New(func(resolve func(string), reject func(error)) {
time.Sleep(time.Millisecond * 100)
resolve("a")
})
p2 := New(func(resolve func(string), reject func(error)) {
time.Sleep(time.Millisecond * 300)
resolve("b")
})
p := Race([]*Promise[string]{p1, p2})
val, err := p.Await()
assert.Equal("a", val)
assert.IsNil(err)
})
t.Run("EmptyPromises", func(_ *testing.T) {
var empty = []*Promise[any]{}
p := Race(empty)
assert.IsNil(p)
})
t.Run("PromisesContainRejected", func(_ *testing.T) {
p1 := New(func(resolve func(string), reject func(error)) {
time.Sleep(time.Millisecond * 100)
resolve("a")
})
p2 := New(func(resolve func(string), reject func(error)) {
reject(errors.New("error1"))
})
p3 := New(func(resolve func(string), reject func(error)) {
reject(errors.New("error2"))
})
p := Race([]*Promise[string]{p1, p2, p3})
val, err := p.Await()
assert.IsNotNil(err)
// assert.Equal("error1", err.Error())
assert.Equal("", val)
})
t.Run("PromisesOnlyRejected", func(_ *testing.T) {
p1 := New(func(resolve func(string), reject func(error)) {
reject(errors.New("error1"))
})
p2 := New(func(resolve func(string), reject func(error)) {
reject(errors.New("error2"))
})
p3 := New(func(resolve func(string), reject func(error)) {
reject(errors.New("error3"))
})
p := Race([]*Promise[string]{p1, p2, p3})
_, err := p.Await()
assert.IsNotNil(err)
// assert.Equal("error1", err.Error())
})
}