In Dart, what does a function marked `async` return to its caller, and what does `await` do inside it?
answer
- always a future, never the raw value
- return value completes it
- throw completes it with an error
- synchronous until the first await
- await suspends, then resumes or throws
basics
~20 sAn async Dart function always returns a Future<T> immediately; returning a value completes it and throwing completes it with an error. Inside, await suspends the function until a future completes, then resumes with its value or rethrows its error.
solid answer
~40 sMarking a body `async` makes the function return a `Future<T>` no matter what the body does: `return 42;` in a `Future<int>` function completes that future with `42`, and a `throw` completes it with an error rather than throwing out of the call. The body runs **synchronously until its first `await`**; at that point the function suspends, the caller gets the pending future and carries on, and the function resumes later when the awaited future completes. `await` evaluates to the completed value, or rethrows the error so an ordinary `try`/`catch` around it works. A function with nothing to return should be declared `Future<void>` so callers can still await it, and `await` is only allowed inside an `async` body.
code
dart · 14 linesFuture<String> greet(String name) async {
if (name.isEmpty) throw ArgumentError('name is empty');
await Future.delayed(const Duration(milliseconds: 100));
return 'Hello, $name';
}
Future<void> main() async {
print(await greet('Dash')); // Hello, Dash
try {
await greet('');
} on ArgumentError catch (e) {
print('caught: ${e.message}'); // caught: name is empty
}
}go deeper
Say plainly that an async function returns a Future, that return completes it and throw fails it, and that await gives you the value or rethrows the error.
Explain that the body runs synchronously until the first await, what the caller receives at that point, and why Future<void> beats void for async functions.
Diagnose bugs where a Future leaked through as a value, a try without await caught nothing, or code before the first await ran earlier than expected.
Set conventions for async APIs: Future<void> over void, no needless async wrappers, and lints that keep futures from silently escaping.
## What `async` changes about a function A `Future<T>` in Dart is an object representing a value of type `T` (or an error) that will be available later. Adding `async` before a function body changes the function's contract in three ways: 1. **The return value is always a future.** The declared type is `Future<T>`, and the caller receives that future immediately, before the work finishes. 2. **`return` completes the future.** Inside the body you return a plain `T`; Dart wraps it. Returning a `Future<T>` also works: the result follows that future. 3. **`throw` completes the future with an error.** An exception inside an `async` body never escapes the call synchronously, even if it happens before the first `await`. The caller sees it only when it awaits the future or attaches an error handler. ```dart Future<int> loadScore() async { final raw = await fetchRaw(); // suspends here if (raw.isEmpty) throw FormatException('empty'); return int.parse(raw); // completes the future } ``` ## When the body actually runs The dart.dev async guide states the rule precisely: **an `async` function runs synchronously until the first `await`**. Everything before that first `await` executes during the call itself, on the caller's stack. When execution reaches `await`, the function **suspends**: it hands the still-pending future back to the caller, and the caller continues with its next statement. Later, when the awaited future completes, the rest of the body resumes. ```dart Future<void> f() async { print('A'); await null; print('C'); } void main() { f(); print('B'); } // prints A, B, C ``` Nothing here involves another thread. A Dart isolate runs one piece of code at a time; `await` simply lets other pending work run while this function waits. ## What `await` evaluates to - If the awaited future completes with a **value**, the `await` expression evaluates to that value, fully typed: `await Future<int>` is an `int`. - If it completes with an **error**, `await` **rethrows** that error at that line, with its stack trace, so `try`/`catch`/`finally` around awaits behaves just like synchronous code. - Awaiting a non-future value is allowed but pointless; the `await_only_futures` lint (core set) flags it, with `await null` accepted as a deliberate yield. - `await` is legal only inside a function body marked `async` (or `async*`). ## Return types and common mistakes | Declaration | What the caller gets | Verdict | |---|---|---| | `Future<int> load() async` | a future completing with an `int` | normal | | `Future<void> save() async` | a future to await for completion and errors | normal for no-value work | | `void save() async` | nothing it can await | fire-and-forget only; `avoid_void_async` flags it | | `int load() async` | compile error | an `async` body cannot return a bare `int` type | Mistakes interviewers listen for: - **Forgetting `await`**: `final score = loadScore();` makes `score` a `Future<int>`, not an `int`. The analyzer usually catches it when the variable is used as an `int`, but not when it is only printed or passed along as an `Object`. - **Expecting a synchronous throw**: wrapping `loadScore()` in `try` without `await` catches nothing, because the error lands in the future. - **Adding `async` for no reason**: Effective Dart says not to use `async` when it has no useful effect, such as a function that only returns another future. ## `async` without `await` An `async` body that never awaits is legal, and sometimes it is the shortest correct code. Effective Dart lists the cases where `async` still earns its place: - **Returning an error asynchronously**: `async` plus `throw` is shorter than `return Future.error(...)`. - **Wrapping a plain value**: `Future<String> label() async => 'ready';` is shorter than `Future.value('ready')`. - **Using `await`**, the obvious case. The opposite advice also holds: if removing `async` does not change behaviour, for example a function that only returns another future, such as `return Future.any([left, right]);`, leave it off. ## Why this matters in practice Knowing exactly what an `async` function returns explains most day-to-day async bugs: a value that is still a `Future`, an error that was never awaited, a statement that ran earlier than expected because everything before the first `await` is synchronous. It is the foundation for combining futures with `Future.wait`, adding a `timeout`, or deciding when a future may deliberately go unawaited.
- Why should an async function with no result return `Future<void>` rather than `void`?A `Future<void>` lets callers await completion and receive errors; a `void` async function gives them nothing to wait on, so its failures surface only as unhandled errors and ordering is lost. Effective Dart asks for `Future<void>`, and the opt-in `avoid_void_async` lint flags `void f() async`, with an exception for a top-level `main`.
- If an async function throws before its first await, does the caller's try/catch around the call catch it?Only if the caller awaits. The exception completes the returned future with an error instead of propagating synchronously, so `try { load(); } catch (_) {}` without `await` catches nothing, while `try { await load(); } catch (_) {}` does.
saying these in an interview costs you the question
- An async function returns its value directly once it finishes
- await blocks the thread until the future completes
- Code before the first await runs later, on the event loop
- An exception before the first await is thrown synchronously to the caller
- void is the right return type for an async function with no result