# Async I/O Design ## Async functions in different languages ### JavaScript - [Async/Await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) Prototype: ```javascript async function name(param0) { statements; } async function name(param0, param1) { statements; } async function name(param0, param1, /* …, */ paramN) { statements; } ``` Example: ```typescript async function resolveAfter1Second(): Promise { return new Promise((resolve) => { setTimeout(() => { resolve("Resolved after 1 second"); }, 1000); }); } async function asyncCall(): Promise { const result = await resolveAfter1Second(); return `AsyncCall: ${result}`; } function asyncCall2(): Promise { return resolveAfter1Second(); } function asyncCall3(): void { resolveAfter1Second().then((result) => { console.log(`AsyncCall3: ${result}`); }); } async function main() { console.log("Starting AsyncCall"); const result1 = await asyncCall(); console.log(result1); console.log("Starting AsyncCall2"); const result2 = await asyncCall2(); console.log(result2); console.log("Starting AsyncCall3"); asyncCall3(); // Wait for AsyncCall3 to complete await new Promise((resolve) => setTimeout(resolve, 1000)); console.log("Main function completed"); } main().catch(console.error); ``` ### Python - [async def](https://docs.python.org/3/library/asyncio-task.html#coroutines) Prototype: ```python async def name(param0): statements ``` Example: ```python import asyncio async def resolve_after_1_second() -> str: await asyncio.sleep(1) return "Resolved after 1 second" async def async_call() -> str: result = await resolve_after_1_second() return f"AsyncCall: {result}" def async_call2() -> asyncio.Task: return resolve_after_1_second() def async_call3() -> None: asyncio.create_task(print_after_1_second()) async def print_after_1_second() -> None: result = await resolve_after_1_second() print(f"AsyncCall3: {result}") async def main(): print("Starting AsyncCall") result1 = await async_call() print(result1) print("Starting AsyncCall2") result2 = await async_call2() print(result2) print("Starting AsyncCall3") async_call3() # Wait for AsyncCall3 to complete await asyncio.sleep(1) print("Main function completed") # Run the main coroutine asyncio.run(main()) ``` ### Rust - [async fn](https://doc.rust-lang.org/std/keyword.async.html) Prototype: ```rust async fn name(param0: Type) -> ReturnType { statements } ``` Example: ```rust use std::time::Duration; use tokio::time::sleep; use std::future::Future; async fn resolve_after_1_second() -> String { sleep(Duration::from_secs(1)).await; "Resolved after 1 second".to_string() } async fn async_call() -> String { let result = resolve_after_1_second().await; format!("AsyncCall: {}", result) } fn async_call2() -> impl Future { resolve_after_1_second() } fn async_call3() { tokio::spawn(async { let result = resolve_after_1_second().await; println!("AsyncCall3: {}", result); }); } #[tokio::main] async fn main() { println!("Starting AsyncCall"); let result1 = async_call().await; println!("{}", result1); println!("Starting AsyncCall2"); let result2 = async_call2().await; println!("{}", result2); println!("Starting AsyncCall3"); async_call3(); // Wait for AsyncCall3 to complete sleep(Duration::from_secs(2)).await; println!("Main function completed"); } ``` ### C# - [async](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/) Prototype: ```csharp async Task NameAsync(Type param0) { statements; } ``` Example: ```csharp using System; using System.Threading.Tasks; class Program { static async Task ResolveAfter1Second() { await Task.Delay(1000); return "Resolved after 1 second"; } static async Task AsyncCall() { string result = await ResolveAfter1Second(); return $"AsyncCall: {result}"; } static Task AsyncCall2() { return ResolveAfter1Second(); } static void AsyncCall3() { _ = Task.Run(async () => { string result = await ResolveAfter1Second(); Console.WriteLine($"AsyncCall3: {result}"); }); } static async Task Main() { Console.WriteLine("Starting AsyncCall"); string result1 = await AsyncCall(); Console.WriteLine(result1); Console.WriteLine("Starting AsyncCall2"); string result2 = await AsyncCall2(); Console.WriteLine(result2); Console.WriteLine("Starting AsyncCall3"); AsyncCall3(); // Wait for AsyncCall3 to complete await Task.Delay(1000); Console.WriteLine("Main method completed"); } } ``` ### C++ 20 Coroutines - [co_await](https://en.cppreference.com/w/cpp/language/coroutines) Prototype: ```cpp TaskReturnType NameAsync(Type param0) { co_return co_await expression; } ``` Example: ```cpp #include #include #include #include #include #include cppcoro::task resolveAfter1Second() { co_await std::chrono::seconds(1); co_return "Resolved after 1 second"; } cppcoro::task asyncCall() { auto result = co_await resolveAfter1Second(); co_return "AsyncCall: " + result; } cppcoro::task asyncCall2() { return resolveAfter1Second(); } cppcoro::task asyncCall3() { auto result = co_await resolveAfter1Second(); std::cout << "AsyncCall3: " << result << std::endl; } cppcoro::task main() { std::cout << "Starting AsyncCall" << std::endl; auto result1 = co_await asyncCall(); std::cout << result1 << std::endl; std::cout << "Starting AsyncCall2" << std::endl; auto result2 = co_await asyncCall2(); std::cout << result2 << std::endl; std::cout << "Starting AsyncCall3" << std::endl; auto asyncCall3Task = asyncCall3(); // Wait for AsyncCall3 to complete co_await asyncCall3Task; std::cout << "Main function completed" << std::endl; } int main() { try { cppcoro::sync_wait(::main()); } catch (const std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; return 1; } return 0; } ``` ## Common concepts ### Promise, Future, Task, and Coroutine - **Promise**: An object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value. It is used to produce a value that will be consumed by a `Future`. - **Future**: An object that represents the result of an asynchronous operation. It is used to obtain the value produced by a `Promise`. - **Task**: A unit of work that can be scheduled and executed asynchronously. It is a higher-level abstraction that combines a `Promise` and a `Future`. - **Coroutine**: A special type of function that can suspend its execution and return control to the caller without losing its state. It can be resumed later, allowing for asynchronous programming. ### `async`, `await` and similar keywords - **`async`**: A keyword used to define a function that returns a `Promise` or `Task`. It allows the function to pause its execution and resume later. - **`await`**: A keyword used to pause the execution of an `async` function until a `Promise` or `Task` is resolved. It unwraps the value of the `Promise` or `Task` and allows the function to continue. - **`co_return`**: A keyword used in C++ coroutines to return a value from a coroutine. It is similar to `return` but is used in coroutines to indicate that the coroutine has completed. It's similar to `return` in `async` functions in other languages that boxes the value into a `Promise` or `Task`. `async/await` and similar constructs provide a more readable and synchronous-like way of writing asynchronous code, it hides the type of `Promise`/`Future`/`Task` from the user and allows them to focus on the logic of the code. ### Executing Multiple Async Operations Concurrently To run multiple promises concurrently, JavaScript provides `Promise.all`, `Promise.allSettled` and `Promise.any`, Python provides `asyncio.gather`, Rust provides `tokio::try_join`, C# provides `Task.WhenAll`, and C++ provides `cppcoro::when_all`. In some situations, you may want to get the first result of multiple async operations. JavaScript provides `Promise.race` to get the first result of multiple promises. Python provides `asyncio.wait` to get the first result of multiple coroutines. Rust provides `tokio::select!` to get the first result of multiple futures. C# provides `Task.WhenAny` to get the first result of multiple tasks. C++ provides `cppcoro::when_any` to get the first result of multiple tasks. Those functions are very simular to `select` in Go. ### Error Handling `await` commonly unwraps the value of a `Promise` or `Task`, but it also propagates errors. If the `Promise` or `Task` is rejected or throws an error, the error will be thrown in the `async` function by the `await` keyword. You can use `try/catch` blocks to handle errors in `async` functions. ## Common patterns - `async` keyword hides the types of `Promise`/`Future`/`Task` in the function signature in Python and Rust, but not in JavaScript, C#, and C++. - `await` keyword unwraps the value of a `Promise`/`Future`/`Task`. - `return` keyword boxes the value into a `Promise`/`Future`/`Task` if it's not already. ## Design considerations in LLGo - Don't introduce `async`/`await` keywords to compatible with Go - For performance and memory reasons don't implement async functions with goroutines, coroutines, or other mechanisms that require per-task stack allocation - Avoid implementing async task by using `chan` that blocking the thread ## Design ### `async.Future[T]` type Introduce `async.Future[T]` type to represent an eventual completion (or failure) of an asynchronous operation and its resulting value, similar to `Promise`/`Future` in other languages. Functions that return `async.Future[T]` are considered asynchronous functions. ### Future creation `async.Future[T]` can be created by `async.Async[T]` function that takes a function that accepts a `resolve` function to produce a value of type `T`. ### Future chaining (asynchronous callbacks style) `async.Future[T]` can be chained with `Then` method to add multiple callbacks to be executed when the operation is completed, it just runs once and calls every callbacks. Currently `Then` method can't be chained multiple times because Go doesn't support generics method (Need support `func (f Future[T]) Then[U any](f func(T) Future[U]) Future[U]`), maybe implements in Go+. ### Future waiting (synchronous style) `async.Await[T]` function can be used to wait for the completion of a `Future[T]` and return the value produced by the operation. In LLGo, `async.Await[T]` is a blocking function that waits for the completion of the `Future[T]` and returns the value synchronously, it would be transformed to `Future.Then` callback in the frontend. ### `async.Run[T]` function `async.Run[T]` function can be used to create an global asynchronous context and run async functions, and it would be hidden by the compiler in the future. Currently it will switch the callbacks to the goroutine that calls `async.Run[T]` function, this maybe changed in the future to reduce the overhead of switching goroutines and make it more parallel. ### Prototype ```go package async type Future[T any] interface { Then(f func(T)) } func Async[T any](f func(resolve func(T))) Future[T] func Await[T any](future Future[T]) T ``` ### Some async functions ```go package async func Race[T1 any](futures ...Future[T1]) Future[T1] func All[T1 any](futures ...Future[T1]) Future[[]T1] ``` ### Example ```go package main func main() { async.Run(func() { hello := func() async.Future[string] { return async.Async(func(resolve func(string)) { resolve("Hello, World!") }) } future := hello() future.Then(func(value string) { println("first callback:", value) }) future.Then(func(value string) { println("second callback:", value) }) println("first await:", async.Await(future)) println("second await:", async.Await(future)) }) } ```