Skip to content

Alexander-Miller/pfuture

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

46 Commits
 
 
 
 
 
 

Repository files navigation

Content

Pfuture

What it is

pfuture.el offers a set of simple functions wrapping Emacs’ existing process creation capabilities. It allows to conveniently deal with external processes in an asynchronous manner without having to worry about stdout buffers and filter- & sentinel-functions.

The following examples practically demonstrate its capabilities. Detailed and formal documentation can be found in each function’s eldoc.

Practical examples

Pfuture has 2 entry points.

  • pfuture-new creates a future object that can be stored, passed around to other functions and awaited to completion.
  • pfuture-callback allows starting an external process in a fire-and-forget fashion, alongside callbacks to execute when the process succeeds or fails, with full access to its output.

With a Future object

We can use pfuture to start an artficially long-running process (simulated with a sleep of 3 seconds) twice and wait until both futures complete. Despite sleeping twice only 3 seconds will have passed since the processes run in parallel.

(let ((start   (float-time))
      (future1 (pfuture-new "sleep" "3"))
      (future2 (pfuture-new "sleep" "3"))
      (future3 (pfuture-new "echo" "All futures have finished after %s seconds.")))
  (pfuture-await future1 :timeout 4 :just-this-one nil)
  (pfuture-await future2 :timeout 4 :just-this-one nil)
  (pfuture-await future3 :timeout 4 :just-this-one nil)
  (message (pfuture-result future3) (round (- (float-time) start))))

Stdout and stderr in future objects are separate:

(let ((future (pfuture-new "ls" "nonexsitent_file")))
  (pfuture-await-to-finish future)
  (message "Future stdout: [%s]" (string-trim (pfuture-result future)))
  (message "Future stderr: [%s]" (string-trim (pfuture-stderr future))))

Calls to pfuture-await (and especially pfuture-await-to-finish) are blocking, so it is important to set an appropriate timeout (default is 1 second) or to be really sure that the process is going to terminate.

With a Callback

Here we start another process and instead of keeping the future around and eventually awaiting it we can simply define what steps to take once the process has completed, depending on whether it failed or not.

(Note that pfuture-callback requires lexical scope)

(defun showcase-error-callback (process status output)
  (message "Pfuture Error!")
  (message "Process: %s" process)
  (message "Status: %s" status)
  (message "Output: %s" output))

(let ((debug-callback (lambda (pfuture-process status _pfuture-buffer)
                        (message "Pfuture Debug: Process [%s] changed sttaus to [%s]" pfuture-process status))))
  (pfuture-callback ["ls" "-alh" "."]
    :directory "~/Documents/git/pfuture"
    :name "Pfuture Example"
    :on-success (message "Pfuture Finish:\n%s" (pfuture-callback-output))
    :on-error #'showcase-error-callback
    :on-status-change debug-callback))

About async.el

You might be inclined to compare both packages since they both, at first glance, handle asynchronous processes, but in truth they have very little in common outside of their general asynchronous nature.

Async.el allows you to start and handle an asynchronous Emacs instance, running Elisp code. Pfuture lets you start any external command like git or ls (as its mostly a wrapper around make-process), and then read its output. So while the two packages may appear similar at first there is really nothing to compare.