Deferred Resolution
When to Use
You have an async operation that completes later (timer, network, thread pool) and want to create a Promise that resolves when it finishes.
xpp::async<T>()
Returns std::pair<Promise<T>, PromiseResolver<T>>. The resolver is safe to call after the Promise is destroyed — it holds ArcWeak, so resolve() silently drops if the Promise is gone.
#include <xpp/promise.h>
#include <xpp/promise_adapter.h>
xpp::EventLoop loop;
xpp::WaitScope scope(loop);
auto [p, r] = xpp::async<int>();
// Resolve from a timer callback
xpp::Timer t(100, 0, [&]() { r.resolve(42); });
int result = p.await(); // blocks ~100ms
// result == 42
Cross-Thread Resolve
resolve() is thread-safe — ArcWeak::upgrade() is a CAS loop. Safe to call from any thread.
#include <thread>
xpp::EventLoop loop;
xpp::WaitScope scope(loop);
auto [p, r] = xpp::async<std::string>();
std::thread worker([&]() {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
r.resolve(std::string("from another thread"));
});
std::string result = p.await();
// result == "from another thread"
worker.join();
Safe After Promise Destruction
If the Promise is destroyed before resolve() is called (e.g., a losing branch in race()), resolve() silently drops — no UAF.
xpp::EventLoop loop;
xpp::WaitScope scope(loop);
xpp::PromiseResolver<int> r;
{
auto [p, r2] = xpp::async<int>();
r = std::move(r2);
// p is destroyed when the scope ends
}
// Promise is gone. resolve() safely drops.
r.resolve(42);
// No crash.
Double Resolve
Only the first resolve() takes effect. Subsequent calls are silently dropped via compare_exchange_strong on the resolved flag.
auto [p, r] = xpp::async<int>();
r.resolve(42);
r.resolve(99); // silently dropped
EXPECT_EQ(p.await(), 42);
Nested wait()
wait() inside a .then() callback is safe — WaitScope owns the loop binding, nested xEventLoopRun doesn't unbind it.
auto [outer_p, outer_r] = xpp::async<int>();
auto [inner_p, inner_r] = xpp::async<int>();
// Schedule: outer at 60ms, inner at 30ms
// ...
int result = outer_p
.then([&](int outer_val) {
int inner_val = inner_p.await(); // nested Run
return outer_val + inner_val;
})
.await();
Best Practices
PromiseResolvercan safely outlive the Promise.ArcWeak—resolve()silently drops. No UAF.is_pending()is not atomic. Check only from the owner thread.- Don't
wait()on an empty promise. Checkoperator bool()first. - Nested
wait()is safe but beware deadlocks. If the inner promise is never resolved,wait()spins indefinitely.