The BEAM is also more or less an abstraction around an event loop for async i/o. If you want async i/o in nifs, I think you want to integrate with BEAM's event loop. Inside NIFs, I think you want to use enif_select [1] (and friends), available since OTP 20 originally from 2017-06-21. In a port driver, you'd use driver_select [2] which I think has been around forever --- there's mentions of changes in R13 which I think was mostly release 2009-11-20 (that may have been R13B though).
[1] https://www.erlang.org/doc/apps/erts/erl_nif.html#enif_selec...
[2] https://www.erlang.org/doc/apps/erts/erl_driver.html#driver_...