# Async functions
**Author**: Wes Billman
**Status**: ==draft== / in-review / accepted / implemented / rejected
## Motivation
The ability to pass function references through the RPC subsystem. Very useful for callbacks.
## Goals
* Support function references
* Alllow those references to be optional
* Keep a consistant Verb interface
## Design
The initial approach will be to add a new method to the `ftl` SDK. This new method will enable Async calls, which will have a Promise style interface.
> TODO: is this signature correct? Do we want the req as well?
```go
func AsyncCall[Req, Resp any](
ctx context.Context,
verb Verb[Req, Resp],
req Req,
callback Verb[AsyncResult[Req, Resp], ftl.Void]) error {
// ... call controller
return nil
}
```
```protobuf
message AsyncCallRequest {
Metadata metadata = 1;
schema.VerbRef verb = 2;
bytes body = 3;
schema.VerbRef callback = 4;
}
message AsyncCallResponse {
string id = 1;
optional Error error = 2;
}
service VerbService {
// ...
rpc AsyncCall(AsyncCallRequest) AsyncCallResponse
}
```
```go
//ftl:verb
func CreateAvatars(ctx context.Context, req CreateAvatarsRequest) (CreateAvatarsResponse, error) {
// ...
}
```
Schema syntax???
```go
verb onAvatarsCreated(result CreateAvatarsResponse)
```
An example usage of this might be...
```go
type SignupRequest struct {
Email string
}
type SignupResponse struct {
Message string
}
//ftl:verb
func Signup(ctx context.Context, req SignupRequest) (SignupResponse, error) {
err := ftl.AsyncCall(ctx, images.CreateAvatars, images.CreateAvatarsRequest{}, OnAvatarsCreated)
if err != nil {
return SignupResponse{}, err
}
return SignupResponse{Message: fmt.Sprintf("Hello, %s!", req.Name)}, nil
}
//ftl:verb
func OnAvatarsCreated(
ctx context.Context,
// Do we want request as well here?
req AsyncResult[images.CreateAvatarsRequest, images.CreateAvatarsResponse],
) error {
// Do something with the avatars. Maybe save their paths to the database?
}
```
## Roadmap
Rough roadmap of tasks and time estimates for how long the implementation will take.
## Alternatives considered
### Callback functions in data types
```go
module user {
data EnrolledUserRequest {}
data EnrolledUserResponse {}
data EnrollUserRequest {
onComplete verb(EnrolledUserRequest) EnrolledUserResponse
}
verb enroll(EnrollUserRequest) EnrollUserResponse
}
module signup {
verb signup(SignupRequest) SignupResponse
verb onUserEnrolled(user.EnrolledUserRequest) user.EnrolledUserResponse
}
```
```go
//ftl:verb
func Signup(ctx context.Context, req SignupRequest) (SignupResponse, error) {
var callback = OnUserEnrolled
err := ftl.AsyncCall(ctx, req.Enroll, user.EnrollUserRequest{...}, OnUserEnrolled)
resp, err := ftl.Call(ctx, user.Enroll, user.EnrollUserRequest{Name: "foo", OnComplete: callback})
}
//ftl:verb
func OnUserEnrolled(ctx context.Context, req user.EnrolledUserRequest) (user.EnrolledUserResponse, error) {
// ...
}
```
```go
module user {
data SignupRequest {
Email String
}
data SignupResponse {}
verb signUp()
}
err := ftl.AsyncCall(ctx, req.Enroll, user.EnrollUserRequest{}, OnUserEnrolled)
```
```json
{"name": "Alice", "onComplete": "user.enroll"}
```
```go
var VerbMapping = map[string]any {
"user.enroll": Enroll,
}
```
```go
module user {
data EnrolledUserRequest {}
data EnrolledUserResponse {}
data EnrollUserRequest {
name String
onComplete verb(EnrolledUserRequest) EnrolledUserResponse
// optional signature
onComplete (verb(EnrolledUserRequest) EnrolledUserResponse)?
}
verb enroll(EnrollUserRequest) EnrollUserResponse
}
module signup {
verb signup(SignupRequest) SignupResponse
verb onUserEnrolled(user.EnrolledUserRequest) user.EnrolledUserResponse
}
```
```go
type EnrollUserRequest struct {
Name string
OnComplete func(user.EnrolledUserRequest) (user.EnrolledUserResponse, error)
}
func Enroll(ctx context.Context, req EnrollUserRequest) (EnrollUserResponse, error) {
// Kick off some async thing and report back later
if req.onComplete != nil {
// How do we call req.OnComplete here?
ftl.Call(ctx, req.OnComplete, user.EnrolledUserRequest{})
}
}
```