Asynchronous Flow Control

Bnlang's runtime is built on a single-threaded event loop (powered by libuv). Long-running operations — disk I/O, network requests, timers — don't block the loop; they schedule a callback that fires when the work is done.

This means asynchronous code in Bnlang is callback-based. There are no Promises and no async/await keywords — the contract is simpler: pass a function, get called back.


Error-First Callbacks

Standard library functions that take a callback follow the error-first convention: the first argument is null on success, or an error message string on failure.

import "io" as io;

io.read_file_async("notes.txt", function (err, data) {
    if (err != null) {
        print("read failed:", err);
        return;
    }
    print("got", data.byte_length, "bytes");
});

Deferring Work with Timers

The timers module schedules a callback to fire later from the event loop.

import "timers" as timers;

print("a");
timers.set(0,    function () { print("c — next tick"); });
timers.set(1000, function () { print("d — one second later"); });
print("b");
// Output order:
// a
// b
// c — next tick
// d — one second later

Composing Async Steps

For sequential async work, you nest callbacks or factor each step into a named function. Keep handlers small to keep the nesting shallow.

import "io" as io;

function on_written(err) {
    if (err != null) { print("write failed:", err); return; }
    io.read_file_async("greeting.txt", on_read);
}

function on_read(err, data) {
    if (err != null) { print("read failed:", err); return; }
    print("file says:", data);
}

io.write_file_async("greeting.txt", "Hello, async!", on_written);
print("scheduled");

Best Practices

  • Always check the error argument first; bail out early before touching the result.
  • Pull each async step into a named function once nesting reaches two or three levels.
  • Clear timers and close streams you no longer need — the event loop will hold them alive otherwise.