Здесь есть пара вещей, которые, я думаю, сбивают вас с толку. Думаю, я могу все прояснить.
await GLib.spawn_async_with_pipes(
GLib имеет свою собственную концепцию асинхронных функций, которую при необходимости необходимо заключить в Promise для эффективной работы с ключевым словом await
. В этом случае GLib.spawn_async_with_pipes()
не является асинхронным, как вы думаете, но это нормально, потому что мы собираемся использовать класс более высокого уровня Gio.Subprocess
.
async function mail(msgObj, to, host, user, pass, cancellable = null) {
try {
let proc = new Gio.Subprocess({
argv: ['mail',
'-V',
// Option switches and values are separate args
'-s', `"${msgObj.subBlock}"`,
'-r', `${to}`,
'-S', `smtp=${host}`,
'-S', 'smtp-use-starttls',
'-S', 'smtp-auth=login',
'-S', `smtp-auth-user=${user}`,
'-S', `smtp-auth-password=${pass}`,
FROM
],
flags: Gio.SubprocessFlags.STDIN_PIPE |
Gio.SubprocessFlags.STDOUT_PIPE |
Gio.SubprocessFlags.STDERR_MERGE
});
// Classes that implement GInitable must be initialized before use, but
// you could use Gio.Subprocess.new(argv, flags) which will call this for you
proc.init(cancellable);
// We're going to wrap a GLib async function in a Promise so we can
// use it like a native JavaScript async function.
//
// You could alternatively return this Promise instead of awaiting it
// here, but that's up to you.
let stdout = await new Promise((resolve, reject) => {
// communicate_utf8() returns a string, communicate() returns a
// a GLib.Bytes and there are "headless" functions available as well
proc.communicate_utf8_async(
// This is your stdin, which can just be a JS string
msgObj.msgBlock,
// we've been passing this around from the function args; you can
// create a Gio.Cancellable and call `cancellable.cancel()` to
// stop the command or any other operation you've passed it to at
// any time, which will throw an "Operation Cancelled" error.
cancellable,
// This is the GAsyncReady callback, which works like any other
// callback, but we need to ensure we catch errors so we can
// propagate them with `reject()` to make the Promise work
// properly
(proc, res) => {
try {
let [ok, stdout, stderr] = proc.communicate_utf8_finish(res);
// Because we used the STDERR_MERGE flag stderr will be
// included in stdout. Obviously you could also call
// `resolve([stdout, stderr])` if you wanted to keep both
// and separate them.
//
// This won't affect whether the proc actually return non-
// zero causing the Promise to reject()
resolve(stdout);
} catch (e) {
reject(e);
}
}
);
});
return stdout;
} catch (e) {
// This could be any number of errors, but probably it will be a GError
// in which case it will have `code` property carrying a GIOErrorEnum
// you could use to programmatically respond to, if desired.
logError(e);
}
}
Gio.Subprocess
является лучшим выбором в целом, но особенно для языковых привязок, которые не могут передавать «аргументы» в функции. Используя GLib.spawn_async_with_pipes
, вы обычно вводите NULL
, чтобы предотвратить открытие труб, которые вам не нужны, и всегда гарантируете закрытие труб, которые вам не нужны. Поскольку мы не можем сделать это в GJS, вы можете получить висячие файловые дескрипторы, которые вы не сможете закрыть.
Gio.Subprocess
выполняет большую часть работы за вас и обеспечивает закрытие дескрипторов файлов, предотвращает процессы зомби, настраивает дочерние часы для вас и другие вещи, о которых вы действительно не хотите беспокоиться. Он также имеет удобные функции для получения потоков ввода-вывода, поэтому вам не придется самостоятельно оборачивать fd, среди других полезных вещей.
Я написал более длинный учебник по асинхронному программированию в GJS, который может оказаться полезным здесь . Вы должны быть в состоянии справиться с этим довольно быстро, и он попытается прояснить некоторую путаницу в отношениях между асинхронностью GLib, асинхронностью JavaScript и главным циклом GLib против цикла событий JS.