examples/Asio/Client/README.md
Educational tutorial for FastLED's HTTP fetch API (fl::fetch) with explicit types for learning.
This comprehensive tutorial demonstrates 4 different async approaches for HTTP networking in FastLED:
.then() and .catch_() callbacksfl::await_top_level() patternThe example cycles through all 4 approaches every 10 seconds with LED feedback to help you understand different async patterns.
All types are explicitly declared (no auto) to help you understand FastLED's async type system:
fl::promise<T> - Represents a future valuefl::result<T> - Wraps success value or errorfl::response - HTTP response with status/headers/bodyfl::optional<T> - May or may not contain a valuefl::Error - Error informationfl::json - JSON parsing with safe access| Color | Approach | Meaning |
|---|---|---|
| Green | Promise-based | HTTP success (.then) |
| Blue | Await-based | HTTP success (await) |
| Blue | JSON Promise | JSON parsed (promise) |
| Cyan | JSON Await | JSON parsed (await) |
| Red | Any | Network or HTTP error |
| Yellow | JSON Promise | Non-JSON response |
| Orange | JSON Await | Non-JSON response |
# For WASM (recommended - full networking)
bash compile wasm --examples Client
# For POSIX (host-based testing)
bash compile posix --examples Client
WASM (browser):
bash run wasm Client
POSIX (console):
.build/meson-quick/examples/Client.exe
For testing without internet connection:
# Terminal 1: Start local test server
uv run python examples/Asio/Client/test_server.py
# Terminal 2: Modify tutorial to use localhost:8081
# (Edit ClientReal.h to change URLs from fastled.io/httpbin.org to localhost:8081)
JavaScript-like async pattern with method chaining:
fl::promise<fl::response> promise = fl::fetch_get("http://fastled.io");
promise.then([](const fl::response& response) {
if (response.ok()) {
FL_WARN("Success: " << response.text());
}
}).catch_([](const fl::Error& error) {
FL_WARN("Error: " << error.message);
});
When to use:
Synchronous-style code that blocks until completion:
fl::promise<fl::response> promise = fl::fetch_get("http://fastled.io");
fl::result<fl::response> result = fl::await_top_level(promise);
if (result.ok()) {
const fl::response& response = result.value();
FL_WARN("Success: " << response.text());
} else {
FL_WARN("Error: " << result.error_message());
}
When to use:
loop() context (safe to block)⚠️ CRITICAL: await_top_level() blocks execution - ONLY use in loop(), NEVER in callbacks!
Promise pattern with automatic JSON parsing:
fl::fetch_get("https://httpbin.org/json").then([](const fl::response& response) {
if (response.is_json()) {
fl::json data = response.json();
fl::string author = data["slideshow"]["author"] | fl::string("unknown");
FL_WARN("Author: " << author);
}
}).catch_([](const fl::Error& error) {
FL_WARN("Error: " << error.message);
});
Features:
| operator)Await pattern with JSON responses:
fl::promise<fl::response> promise = fl::fetch_get("https://httpbin.org/get");
fl::result<fl::response> result = fl::await_top_level(promise);
if (result.ok() && result.value().is_json()) {
fl::json data = result.value().json();
fl::string origin = data["origin"] | fl::string("unknown");
FL_WARN("Origin: " << origin);
}
Combines:
Make HTTP GET request:
// Simple GET
fl::promise<fl::response> promise = fl::fetch_get("http://example.com");
// With options (timeout, headers, etc.)
fl::fetch_options opts("");
opts.timeout(5000).header("User-Agent", "FastLED");
fl::promise<fl::response> promise = fl::fetch_get("http://example.com", opts);
HTTP response object:
Methods:
int status() - HTTP status code (200, 404, etc.)const string& status_text() - Status text ("OK", "Not Found")bool ok() - True if status 200-299const string& text() - Response body as stringfl::json json() - Parse response as JSON (cached)bool is_json() - Check if response is JSONoptional<string> get_content_type() - Get Content-Type headerRepresents future value:
Methods:
promise<T>& then(callback) - Success handlerpromise<T>& catch_(callback) - Error handlerWraps success or error:
Methods:
bool ok() - Check if successfulconst T& value() - Get success value (throws if error)const string& error_message() - Get error messageJSON object with safe access:
Methods:
Json operator[](key) - Access nested valueT operator|(default) - Get value or defaultbool contains(key) - Check if key existsbool is_array() - Check if JSON arraysize_t size() - Array/object sizeThe example automatically cycles through all 4 approaches:
0-10s: Approach 1 (Promise) → Green LEDs on success
10-20s: Approach 2 (Await) → Blue LEDs on success
20-30s: Approach 3 (JSON Promise) → Blue LEDs on success
30-40s: Approach 4 (JSON Await) → Cyan LEDs on success
40s+: Repeats from Approach 1
Watch the serial output and LED colors to understand each approach!
Promise-based:
fetch_get(url).then([](const fl::response& r) {
if (!r.ok()) {
FL_WARN("HTTP error: " << r.status());
}
}).catch_([](const fl::Error& e) {
FL_WARN("Network error: " << e.message);
});
Await-based:
fl::result<fl::response> result = fl::await_top_level(fetch_get(url));
if (!result.ok()) {
FL_WARN("Request failed: " << result.error_message());
}
fl::fetch_options opts("");
opts.header("Authorization", "Bearer token123")
.header("X-Custom", "value")
.timeout(10000); // 10 seconds
fl::fetch_get("http://api.example.com/data", opts);
// Start multiple requests (non-blocking)
fl::fetch_get("http://api1.com/data").then(handle_api1);
fl::fetch_get("http://api2.com/data").then(handle_api2);
fl::fetch_get("http://api3.com/data").then(handle_api3);
// All requests run in parallel!
Cause: CORS policy blocking request
Solution: Use CORS-enabled endpoints or run local test server:
uv run python examples/Asio/Client/test_server.py
Cause: Firewall or no internet connection
Solutions:
Cause: Used await_top_level() in callback context
Solution: Only use await_top_level() in loop(), use .then() in callbacks
ClientValidation.ino for automated test suiteServer.ino for HTTP server example