Need help with emacs-aio?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

skeeto
136 Stars 8 Forks The Unlicense 51 Commits 7 Opened issues

Description

async/await for Emacs Lisp

Services available

!
?

Need anything else?

Contributors list

No Data

aio: async/await for Emacs Lisp

aio
is to Emacs Lisp as
asyncio
is to Python. This package builds upon Emacs 25 generators to provide functions that pause while they wait on asynchronous events. They do not block any thread while paused.

Introduction: An Async / Await Library for Emacs Lisp

Installation is available through MELPA. Since it uses the

record
built-in, it requires Emacs 26 or later.

Usage

An async function is defined using

aio-defun
or
aio-lambda
. The body of such functions can use
aio-await
to pause the function and wait on a given promise. The function continues with the promise's resolved value when it's ready. The package provides a number of functions that return promises, and every async function returns a promise representing its future return value.

For example:

(aio-defun foo (url)
  (aio-await (aio-sleep 3))
  (message "Done sleeping. Now fetching %s" url)
  (let* ((result (aio-await (aio-url-retrieve url)))
         (contents (with-current-buffer (cdr result)
                     (prog1 (buffer-string)
                       (kill-buffer)))))
    (message "Result: %s" contents)))

If an uncaught signal terminates an asynchronous function, that signal is captured by its return value promise and propagated into any function that awaits on that function.

(aio-defun divide (a b)
  (aio-await (aio-sleep 1))
  (/ a b))

(aio-defun divide-safe (a b) (condition-case error (aio-await (divide a b)) (arith-error :arith-error)))

(aio-wait-for (divide-safe 1.0 2.0)) ;; => 0.5

(aio-wait-for (divide-safe 0 0)) ;; => :arith-error

To convert a callback-based function into an awaitable, async-friendly function, create a new promise object with

aio-promise
, then
aio-resolve
that promise in the callback. The helper function,
aio-make-callback
, makes this easy.

Utility macros and functions

(aio-wait-for promise)
;; Synchronously wait for PROMISE, blocking the current thread.

(aio-cancel promise) ;; Attempt to cancel PROMISE, returning non-nil if successful.

(aio-with-promise promise &rest body) [macro] ;; Evaluate BODY and resolve PROMISE with the result.

(aio-with-async &rest body) [macro] ;; Evaluate BODY asynchronously as if it was inside `aio-lambda'.

(aio-make-callback &key tag once) ;; Return a new callback function and its first promise.

(aio-chain expr) [macro] ;; `aio-await' on EXPR and replace place EXPR with the next promise.

The

aio-make-callback
function is especially useful for callbacks that are invoked repeatedly, such as process filters and sentinels. The
aio-chain
macro works in conjunction.

Awaitable functions

Here are some useful promise-returning — i.e. awaitable — functions defined by this package.

(aio-sleep seconds &optional result)
;; Return a promise that is resolved after SECONDS with RESULT.

(aio-idle seconds &optional result) ;; Return a promise that is resolved after idle SECONDS with RESULT.

(aio-url-retrieve url &optional silent inhibit-cookies) ;; Wraps `url-retrieve' in a promise.

(aio-all promises) ;; Return a promise that resolves when all PROMISES are resolved."

Select API

This package includes a select()-like, level-triggered API for waiting on multiple promises at once. Create a "select" object, add promises to it, and await on it. Resolved and returned promises are automatically removed, and the "select" object can be reused.

(aio-make-select &optional promises)
;; Create a new `aio-select' object for waiting on multiple promises.

(aio-select-add select promise) ;; Add PROMISE to the set of promises in SELECT.

(aio-select-remove select promise) ;; Remove PROMISE form the set of promises in SELECT.

(aio-select-promises select) ;; Return a list of promises in SELECT.

(aio-select select) ;; Return a promise that resolves when any promise in SELECT resolves.

For example, here's an implementation of sleep sort:

(aio-defun sleep-sort (values)
  (let* ((promises (mapcar (lambda (v) (aio-sleep v v)) values))
         (select (aio-make-select promises)))
    (cl-loop repeat (length promises)
             for next = (aio-await (aio-select select))
             collect (aio-await next))))

Semaphore API

Semaphores work just as they would as a thread synchronization primitive. There's an internal counter that cannot drop below zero, and

aio-sem-wait
is an awaitable function that may block the asynchronous function until another asynchronous function calls
aio-sem-post
. Blocked functions wait in a FIFO queue and are awoken in the same order that they awaited.
(aio-sem init)
;; Create a new semaphore with initial value INIT.

(aio-sem-post sem) ;; Increment the value of SEM.

(aio-sem-wait sem) ;; Decrement the value of SEM.

This can be used to create a work queue. For example, here's a configurable download queue for

url-retrieve
:
(defun fetch (url-list max-parallel callback)
  (let ((sem (aio-sem max-parallel)))
    (dolist (url url-list)
      (aio-with-async
        (aio-await (aio-sem-wait sem))
        (cl-destructuring-bind (status . buffer)
            (aio-await (aio-url-retrieve url))
          (aio-sem-post sem)
          (funcall callback
                   (with-current-buffer buffer
                     (prog1 (buffer-string)
                       (kill-buffer)))))))))

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.