├── .gitignore ├── LICENSE ├── Readme.md ├── eventloop.go ├── go.mod ├── go.sum └── promise.go /.gitignore: -------------------------------------------------------------------------------- 1 | /example -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Akinlua Bolamigbe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | Go-Promise 2 | 3 | ```go 4 | func GetUserName(id time.Duration) *eventloop.Promise { 5 | return GlobalEventLoop.Async(func() (interface{}, error) { 6 | <-time.After(time.Second * id) 7 | if id == 0 { 8 | return nil, fmt.Errorf("some error id(%s)", id) 9 | } 10 | return fmt.Sprintf("id(%s): Test User", id), nil 11 | }) 12 | } 13 | 14 | func GetUserNameWithPanic() *eventloop.Promise { 15 | return GlobalEventLoop.Async(func() (interface{}, error) { 16 | <-time.After(time.Second * 2) 17 | panic("panic attack") 18 | }) 19 | } 20 | 21 | func main() { 22 | defer GlobalEventLoop.Run() 23 | 24 | result := GetUserName(2) 25 | 26 | result.Then(func(x interface{}) { 27 | fmt.Println("2 : user:", x) 28 | }) 29 | 30 | fmt.Println("run before promise returns") 31 | 32 | GetUserName(0).Then(func(x interface{}) { 33 | fmt.Println("0 : user:", x) 34 | }).Catch(func(err error) { 35 | fmt.Println("0 : err:", err) 36 | }) 37 | 38 | GetUserName(5).Then(func(x interface{}) { 39 | fmt.Println("5 : user:", x) 40 | panic("a panic attack") 41 | }).Catch(func(err error) { 42 | fmt.Println("5 : err:", err) 43 | }) 44 | 45 | GetUserName(15).Then(func(x interface{}) { 46 | fmt.Println("15 : user:", x) 47 | }).Catch(func(err error) { 48 | fmt.Println("15 : err:", err) 49 | }) 50 | 51 | //promise with panic 52 | GetUserNameWithPanic().Then(func(i interface{}) { 53 | fmt.Println("Then block never gets triggered") 54 | }).Catch(func(err error) { 55 | fmt.Println("GetUserNameWithPanic err: ", err) 56 | }) 57 | 58 | // await 59 | syncResult1, err := GetUserName(4).Await() 60 | fmt.Printf("syncResult1 - value: %v, err: %v\n", syncResult1, err) 61 | 62 | syncResult2, err := GetUserName(1).Await() 63 | fmt.Printf("syncResult2 - value: %v, err: %v\n", syncResult2, err) 64 | 65 | asyncResult := GetUserName(0) 66 | GetUserName(3) 67 | 68 | syncResult, err := asyncResult.Await() 69 | fmt.Printf("asyncResult - value: %v, err: %v\n", syncResult, err) 70 | 71 | fmt.Println("done") 72 | 73 | //nested promise 74 | GlobalEventLoop.Async(func() (interface{}, error) { 75 | fmt.Println("outer async") 76 | GlobalEventLoop.Async(func() (interface{}, error) { 77 | fmt.Println("inner async") 78 | return nil, nil 79 | }).Then(func(_ interface{}) { 80 | fmt.Println("resolved inner promise") 81 | }) 82 | <-time.After(time.Second * 2) 83 | return nil, nil 84 | }).Then(func(_ interface{}) { 85 | fmt.Println("resolved outer promise") 86 | }) 87 | } 88 | ``` 89 | 90 | #### result 91 | 92 | ```shell 93 | run before promise returns 94 | 0 : err: some error id(0s) 95 | 2 : user: id(2ns): Test User 96 | GetUserNameWithPanic err: panic attack 97 | syncResult1 - value: id(4ns): Test User, err: 98 | syncResult2 - value: , err: some error id(0s) 99 | asyncResult - value: , err: some error id(0s) 100 | done 101 | outer async 102 | inner async 103 | resolved inner promise 104 | 5 : user: id(5ns): Test User 105 | 5 : err: unknown error: a panic attack 106 | resolved outer promise 107 | 15 : user: id(15ns): Test User 108 | 109 | 110 | ``` 111 | 112 | ## TODO 113 | 114 | - [x] nested promises 115 | - [ ] chained .then 116 | 117 | ```go 118 | GetUserName(7).Then(func(x interface{}) { 119 | fmt.Println("7 (1): user:", x) 120 | }).Then(func(x interface{}) { 121 | fmt.Println("7 (2) : user:", x) 122 | panic("another panic attack") 123 | }).Catch(func(err error) { 124 | fmt.Println("7 : err:", err) 125 | }) 126 | ``` 127 | 128 | - [x] await all promises 129 | 130 | > just a fun project, we might just learn something 131 | -------------------------------------------------------------------------------- /eventloop.go: -------------------------------------------------------------------------------- 1 | package go_future 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | "sync/atomic" 8 | "time" 9 | ) 10 | 11 | var ( 12 | once sync.Once 13 | GlobalEventLoop *eventLoop 14 | ) 15 | 16 | func InitGlobalEventLoop() Future { 17 | once.Do(func() { 18 | var queue []func() bool 19 | var lock sync.Mutex 20 | // event loop 21 | go func() { 22 | for work := range eventBus { 23 | lock.Lock() 24 | queue = append(queue, *work) 25 | lock.Unlock() 26 | } 27 | }() 28 | 29 | go func() { 30 | for { 31 | n := len(queue) 32 | var retry []func() bool 33 | for i := 0; i < n; i++ { 34 | currentWork := queue[i] 35 | 36 | if done := currentWork(); !done { 37 | retry = append(retry, currentWork) 38 | } 39 | } 40 | lock.Lock() 41 | queue = queue[n:] 42 | queue = append(queue, retry...) 43 | lock.Unlock() 44 | } 45 | }() 46 | GlobalEventLoop = &eventLoop{promiseQueue: make([]*Promise, 0)} 47 | }) 48 | return GlobalEventLoop 49 | } 50 | 51 | type Future interface { 52 | Async(fn func() (any, error)) *Promise 53 | Run() 54 | } 55 | 56 | type eventLoop struct { 57 | promiseQueue []*Promise 58 | size int64 59 | sync sync.Mutex 60 | } 61 | 62 | func (e *eventLoop) Run() { 63 | //await all promises 64 | e.awaitAll() 65 | } 66 | 67 | func (e *eventLoop) Async(fn func() (any, error)) *Promise { 68 | resultChan := make(chan any) 69 | errChan := make(chan error) 70 | p := e.newPromise(resultChan, errChan) 71 | go func() { 72 | recoveryHandler := promiseRecovery(resultChan, errChan) 73 | defer func() { 74 | if r := recover(); r != nil { 75 | switch x := r.(type) { 76 | case error: 77 | recoveryHandler(nil, x) 78 | default: 79 | recoveryHandler(nil, fmt.Errorf("%v", x)) 80 | } 81 | } 82 | }() 83 | result, err := fn() 84 | recoveryHandler(result, err) 85 | }() 86 | return p 87 | } 88 | 89 | func promiseRecovery(resultChan chan any, errChan chan error) func(result any, err error) { 90 | return func(result any, err error) { 91 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*1) 92 | defer cancel() 93 | if err != nil { 94 | select { 95 | case errChan <- err: 96 | case <-ctx.Done(): 97 | } 98 | return 99 | } 100 | 101 | select { 102 | case resultChan <- result: 103 | case <-ctx.Done(): 104 | } 105 | } 106 | } 107 | 108 | func (e *eventLoop) awaitAll() { 109 | init := true 110 | outer: 111 | for { 112 | n := len(e.promiseQueue) 113 | if init && n == 0 { 114 | return 115 | } 116 | for i := n - 1; i >= 0; i-- { 117 | e.sync.Lock() 118 | p := e.promiseQueue[i] 119 | e.sync.Unlock() 120 | if p.handler { 121 | <-p.done 122 | } 123 | if currentN := int(atomic.LoadInt64(&e.size)); i == 0 && !(currentN > n) { 124 | close(eventBus) 125 | break outer 126 | } 127 | } 128 | // clean up memory (promise) 129 | e.sync.Lock() 130 | e.promiseQueue = e.promiseQueue[n:] 131 | atomic.AddInt64(&e.size, int64(-n)) 132 | e.sync.Unlock() 133 | init = false 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alob-mtc/go-future 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alob-mtc/go-promise v0.0.0-20220925114035-2f897f94e420 h1:N0GMfuw9xgMnhDCopxbVDlNjPdpBqhOK7MlBoRLV4uw= 2 | github.com/alob-mtc/go-promise v0.0.0-20220925114035-2f897f94e420/go.mod h1:erBnlr0QGzZzpy9ZAfoyd3J8isOvvmgtWWEvtOPuEkI= 3 | -------------------------------------------------------------------------------- /promise.go: -------------------------------------------------------------------------------- 1 | package go_future 2 | 3 | import ( 4 | "fmt" 5 | "sync/atomic" 6 | ) 7 | 8 | var eventBus = make(chan *func() bool) 9 | 10 | type Promise struct { 11 | id uint64 12 | handler bool 13 | rev <-chan interface{} 14 | errChan chan error 15 | doneFlag bool 16 | done chan struct{} 17 | } 18 | 19 | func (e *eventLoop) newPromise(rev <-chan interface{}, errChan chan error) *Promise { 20 | currentP := &Promise{id: uint64(atomic.AddInt64(&e.size, 1)), rev: rev, errChan: errChan, done: make(chan struct{})} 21 | e.sync.Lock() 22 | e.promiseQueue = append(e.promiseQueue, currentP) 23 | e.sync.Unlock() 24 | return currentP 25 | } 26 | 27 | func (p *Promise) complete() { 28 | close(p.done) 29 | } 30 | 31 | func (p *Promise) registerHandler() { 32 | p.handler = true 33 | } 34 | 35 | func (p *Promise) Then(fn func(interface{})) *Promise { 36 | p.registerHandler() 37 | work := func() bool { 38 | select { 39 | default: 40 | if p.doneFlag { 41 | return true 42 | } 43 | return false 44 | case val := <-p.rev: 45 | go func() { 46 | defer func() { 47 | if r := recover(); r != nil { 48 | switch x := r.(type) { 49 | case error: 50 | p.errChan <- x 51 | default: 52 | p.errChan <- fmt.Errorf("%v", x) 53 | } 54 | } else { 55 | p.doneFlag = true 56 | p.complete() 57 | } 58 | }() 59 | fn(val) 60 | }() 61 | return true 62 | } 63 | } 64 | eventBus <- &work 65 | return p 66 | } 67 | 68 | func (p *Promise) Catch(fn func(err error)) { 69 | p.registerHandler() 70 | work := func() bool { 71 | select { 72 | default: 73 | if p.doneFlag { 74 | return true 75 | } 76 | return false 77 | case err := <-p.errChan: 78 | p.doneFlag = true 79 | go fn(err) 80 | p.complete() 81 | return true 82 | } 83 | } 84 | eventBus <- &work 85 | } 86 | 87 | func (p *Promise) Await() (any, error) { 88 | defer p.complete() 89 | p.registerHandler() 90 | select { 91 | 92 | case err := <-p.errChan: 93 | return nil, err 94 | case rev := <-p.rev: 95 | return rev, nil 96 | } 97 | } 98 | --------------------------------------------------------------------------------