Future + wait

Future is bnlang's representation of an asynchronous value. It's a global builtin — no import needed. Combined with the wait keyword, it lets async code read top-to-bottom like synchronous code.

import "request" as r;

function fetch_user(id) {
    var resp = wait r.get("https://api.example.com/users/" + str(id));
    return resp.data;
}

function main() {
    var user = wait fetch_user(7);
    print("hello,", user.name);
}
main().fail(function (e) { print("error:", e); });

A Future is pending, fulfilled with a value, or rejected with an error. It transitions exactly once.

Constructing

Future(executor)

Run executor(resolve, reject) synchronously. Whichever of those is called first determines the Future's outcome.

var f = Future(function (resolve, reject) {
    timers.set(0, function () { resolve("from timer"); });
});

If the executor itself throws, the Future rejects with the thrown value.

Future.of(value)

An already-fulfilled Future. If value is itself a Future, the new one adopts its eventual state.

Future.of(7).next(function (v) { print(v); });   // 7

Future.err(error)

An already-rejected Future.

Future.err("nope").fail(function (e) { print(e); });   // nope

Combinators

Future.all(list)

Wait for every input to fulfill, then fulfill with a list of their values in input order. Reject as soon as any input rejects.

var all = Future.all([
    r.get(url_a),
    r.get(url_b),
    r.get(url_c)
]);
all.next(function (resps) { print(resps[0].body, resps[1].body, resps[2].body); });
// or inside an async fn:
var resps = wait Future.all([r.get(url_a), r.get(url_b)]);

Elements that aren't Futures are auto-wrapped with Future.of, so you can mix.

Future.race(list)

Settle with the first input to settle (fulfill or reject). Other pending inputs keep running but their outcomes are ignored.

Future.all_settled(list)

Never rejects. Always fulfills with a list of result maps, one per input:

Future.all_settled([Future.of("a"), Future.err("b")])
    .next(function (rs) {
        print(rs[0].ok, rs[0].value);     // true a
        print(rs[1].ok, rs[1].error);     // false b
    });

Chain methods

MethodDescription
f.next(on_ok) / f.next(on_ok, on_err)Run on_ok on fulfillment. Returns a new Future of on_ok's return. If on_err is given, also handles rejection.
f.fail(on_err)Run on_err on rejection.
f.always(fn)Run fn regardless of outcome.
f.stateSnapshot string: "pending", "fulfilled", or "rejected".

Callbacks that throw cause the downstream Future to reject with the thrown value. Callbacks that return a Future cause the downstream to adopt its state — so you can chain async work step by step.

Future.of(10)
    .next(function (v) { return v * 2; })           // returns 20
    .next(function (v) { return Future.of(v + 1); }) // adopts → 21
    .next(function (v) { print(v); });              // 21

wait

wait future_expr suspends the current function until the Future settles, then continues with its value. If the Future rejected, wait throws the rejection value (catchable with try/catch).

Supported positions: top-level statement (var x = wait expr; or wait expr;), inside blocks, if/else, while, for-of, and try/catch. Not yet: sub-expression position, finally, C-style for, top-level of a script body.

Any function whose body contains a wait automatically returns a Future. Callers either wait on it or chain with .next/.fail.

function load() {
    try {
        var resp = wait r.get(url);          // can throw
        return resp.body;
    } catch (e) {
        return "fallback";                   // rejection handled
    }
}
load().next(function (body) { print(body); });

Bridging callback-style APIs

futurify(fn)

Wrap a function whose last argument is an (err, result) callback so it returns a Future:

function legacy(x, cb) {
    timers.set(0, function () { cb(null, x * 100); });
}
var f = futurify(legacy);
f(5).next(function (v) { print(v); });     // 500
// or:
var v = wait f(5);

Most lib stdlib modules with async operations already expose a Future-returning surface (request, io, timers.delay, …), so you rarely need futurify directly — it's for your own callback code or third-party native modules.

See also

  • request — HTTP verbs return Futures.
  • io*_async file ops return Futures.
  • timerstimers.delay(ms) is a Future-returning sleep.
  • Keywords — the wait reference.