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.