简体   繁体   中英

How can I schedule something to happen after a series of asynchronous tasks with GJS?

I'm writing a simple desktop application in JavaScript with GJS and the GNOME platform: GTK+, GLib, Gio, GObject. The code below illustrates the situation I'm facing, and is easier to be reproduced because it doesn't need access to the files the application uses. In short, I would like to run the indicated line of code after finishing a series of asynchronous tasks (loading the contents of a series of files). How can I do this in GJS, preferably using something from Gio or maybe from JavaScript itself?

 #!/usr/bin/gjs const Lang = imports.lang; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const Home = new Lang.Class({ Name: "Home", // Start snippet 1 _enumerateChildrenAsyncCallback: function(dir, result) { let fileEnumerator = dir.enumerate_children_finish(result); let displayName, file, fileInfo, fileType, iter; while ((fileInfo = fileEnumerator.next_file(null))) { iter = this.model.append(); displayName = fileInfo.get_display_name(); this.model.set(iter, [0], [displayName], 1); file = dir.get_child(fileInfo.get_name()); fileType = file.query_file_type(Gio.FileQueryInfoFlags.NONE, null); if (fileType != Gio.FileType.REGULAR) continue; file.load_contents_async(null, function(file, result) { let [success, contents, etag] = file.load_contents_finish(result); let message = ""; if (success) { message = "Finished loading file %s"; } else { message = "Couldn't load file %s"; } log(message.replace("%s", file.get_basename())); }); } }, _init: function() { this.application = new Gtk.Application(); this.application.connect("activate", Lang.bind(this, this._onActivate)); this.application.connect("startup", Lang.bind(this, this._onStartup)); }, _onActivate: function() { this._window.show_all(); }, _onStartup: function() { this.model = new Gtk.ListStore(); this.model.set_column_types([GObject.TYPE_STRING]); let renderer = new Gtk.CellRendererText(); let dir = Gio.file_new_for_path(GLib.get_home_dir()); dir.enumerate_children_async("standard::*", Gio.FileQueryInfoFlags.NONE, GLib.PRIORITY_DEFAULT, null, Lang.bind(this, this._enumerateChildrenAsyncCallback), null); /* * I would like this line to be run after all files have been read. * */ this.model.set_sort_column_id(0, Gtk.SortType.ASCENDING); let column = new Gtk.TreeViewColumn({ title: "Files" }); column.pack_start(renderer, true); column.add_attribute(renderer, "text", 0); let view = new Gtk.TreeView({ model: this.model }); view.append_column(column); let scrolled = new Gtk.ScrolledWindow(); scrolled.hscrollbar_policy = Gtk.PolicyType.AUTOMATIC; scrolled.Vscrollbar_policy = Gtk.PolicyType.AUTOMATIC; scrolled.add(view); this._window = new Gtk.ApplicationWindow({ application: this.application, default_height: 300, default_width: 400, title: "Home, sweet home" }); this._window.add(scrolled); } }); let home = new Home(); home.application.run(ARGV); 

PS: In the provided code, running the indicated line of code before finishing all asynchronous tasks doesn't prevent the application from behaving correctly. However, I'd like to sort the list just once in the application startup, instead of sorting the list every time a file is read. And, of course, knowing how to do it can be helpful in another situations, too.

If I understand correctly what you are trying to do, you need something like Promise.all() . Unfortunately, GJS doesn't have a promise library (yet.)

I would suggest doing something like this in your enumerate children callback (I've edited some parts out that are not relevant to what I'm trying to illustrate):

_enumerateChildrenAsyncCallback: function(dir, result) {
  let fileEnumerator = dir.enumerate_children_finish(result);
  let displayName, file, fileInfo, fileType, iter;

  let pendingOperations = new Set();

  while ((fileInfo = fileEnumerator.next_file(null))) {
    file = dir.get_child(fileInfo.get_name());
    fileType = file.query_file_type(Gio.FileQueryInfoFlags.NONE, null);
    if (fileType != Gio.FileType.REGULAR) continue;

    pendingOperations.add(file);

    file.load_contents_async(null, (file, result) => {
      let [success, contents, etag] = file.load_contents_finish(result);

      pendingOperations.delete(file);
      if (pendingOperations.size === 0)
        this.model.set_sort_column_id(0, Gtk.SortType.ASCENDING);
    });
  }
},

At first glance this might appear to have a race condition between pendingOperations clearing out its first element and having a second element added, but the load_contents_async callbacks won't start getting called until the next iteration of the main loop, so you should be safe there.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM