I am trying to run a command from gjs and read the output asynchronously.
here is my synchronous code
let [res, pid, in_fd, out_fd, err_fd] = GLib.spawn_async_with_pipes(null,
['/bin/ls'], null, 0, null);
let out_reader = new Gio.DataInputStream({
base_stream: new Gio.UnixInputStream({fd: out_fd})
});
var out = out_reader.read_until("", null);
print(out);
this works fine but if I try to do it asynchronously it doesn't work
let [res, pid, in_fd, out_fd, err_fd] = GLib.spawn_async_with_pipes(null,
['/bin/ls'], null, 0, null);
let out_reader = new Gio.DataInputStream({
base_stream: new Gio.UnixInputStream({fd: out_fd})
});
function _SocketRead(source_object, res, user_data){
print("hi");
let length;
let out = out_reader.read_upto_finish(asyncResult, length);
print("out" + out);
print("length" + length);
}
var out = out_reader.read_upto_async("",0, 0, null, _SocketRead, "");
while(true){
i = 0;
}
the callback is not called at all
First of all thank you for the question, I also had the same underlying question, that is, your initial line "I am trying to run a command from gjs and read the output asynchronously" and your question had the details I needed to find the solution!
In your example code, the major problem is these lines:
while(true){
i = 0;
}
You are correctly trying to keep the program from terminating before you get the output, but this solution doesn't work.
Javascript is single threaded, meaning that while computations can run concurrently in the serial interleaved sense, there can't be two Javascript computations running in parallel. There is no way to explicitly yield the thread and the busy loop in the question just keeps on spinning and the callback never gets CPU time.
What you want instead is to enter an event loop. If you are developing a Gnome Shell extension, you are already running in one, but if you are just running a script with Gjs, you need to explicitly start one. I'm going to use Clutter, but some other event loop will do just as well. The following code segments constitute a full, working solution.
First of all, let's start by importing needed libraries:
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Clutter = imports.gi.Clutter;
Then add the spawning and file descriptor from the question:
const [res, pid, in_fd, out_fd, err_fd] = GLib.spawn_async_with_pipes(null, ['/bin/ls'], null, 0, null);
const out_reader = new Gio.DataInputStream({
base_stream: new Gio.UnixInputStream({fd: out_fd})
});
Call the async reading function and give it a callback (defined below, usable here thanks to Javascript hoisting):
out_reader.read_upto_async("", 0, 0, null, _SocketRead, "");
And start the event loop:
Clutter.init(null);
Clutter.main();
There were a couple of errors in your callback, so here a fixed version that also terminates the event loop once the command stops producing output:
function _SocketRead(source_object, res){
const [out, length] = out_reader.read_upto_finish(res);
if (length > 0) {
print("out: " + out);
print("length: " + length);
out_reader.read_upto_async("", 0, 0, null, _SocketRead, "");
} else {
Clutter.main_quit();
}
}
For further reading there are Gjs native bindings docs at https://gjs-docs.gnome.org/.