# 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{}) } } ```