dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

README.md (5559B)


      1 # aio: async/await for Emacs Lisp
      2 
      3 `aio` is to Emacs Lisp as [`asyncio`][asyncio] is to Python. This
      4 package builds upon Emacs 25 generators to provide functions that
      5 pause while they wait on asynchronous events. They do not block any
      6 thread while paused.
      7 
      8 Introduction: [An Async / Await Library for Emacs Lisp][post]
      9 
     10 Installation is [available through MELPA][melpa]. Since it uses the
     11 `record` built-in, it requires Emacs 26 or later.
     12 
     13 ## Usage
     14 
     15 An async function is defined using `aio-defun` or `aio-lambda`. The
     16 body of such functions can use `aio-await` to pause the function and
     17 wait on a given promise. The function continues with the promise's
     18 resolved value when it's ready. The package provides a number of
     19 functions that return promises, and every async function returns a
     20 promise representing its future return value.
     21 
     22 For example:
     23 
     24 ```el
     25 (aio-defun foo (url)
     26   (aio-await (aio-sleep 3))
     27   (message "Done sleeping. Now fetching %s" url)
     28   (let* ((result (aio-await (aio-url-retrieve url)))
     29          (contents (with-current-buffer (cdr result)
     30                      (prog1 (buffer-string)
     31                        (kill-buffer)))))
     32     (message "Result: %s" contents)))
     33 ```
     34 
     35 If an uncaught signal terminates an asynchronous function, that signal
     36 is captured by its return value promise and propagated into any
     37 function that awaits on that function.
     38 
     39 ```el
     40 (aio-defun divide (a b)
     41   (aio-await (aio-sleep 1))
     42   (/ a b))
     43 
     44 (aio-defun divide-safe (a b)
     45   (condition-case error
     46       (aio-await (divide a b))
     47     (arith-error :arith-error)))
     48 
     49 (aio-wait-for (divide-safe 1.0 2.0))
     50 ;; => 0.5
     51 
     52 (aio-wait-for (divide-safe 0 0))
     53 ;; => :arith-error
     54 ```
     55 
     56 To convert a callback-based function into an awaitable, async-friendly
     57 function, create a new promise object with `aio-promise`, then
     58 `aio-resolve` that promise in the callback. The helper function,
     59 `aio-make-callback`, makes this easy.
     60 
     61 ## Utility macros and functions
     62 
     63 ```el
     64 (aio-wait-for promise)
     65 ;; Synchronously wait for PROMISE, blocking the current thread.
     66 
     67 (aio-cancel promise)
     68 ;; Attempt to cancel PROMISE, returning non-nil if successful.
     69 
     70 (aio-with-promise promise &rest body) [macro]
     71 ;; Evaluate BODY and resolve PROMISE with the result.
     72 
     73 (aio-with-async &rest body) [macro]
     74 ;; Evaluate BODY asynchronously as if it was inside `aio-lambda'.
     75 
     76 (aio-make-callback &key tag once)
     77 ;; Return a new callback function and its first promise.
     78 
     79 (aio-chain expr) [macro]
     80 ;; `aio-await' on EXPR and replace place EXPR with the next promise.
     81 ```
     82 
     83 The `aio-make-callback` function is especially useful for callbacks
     84 that are invoked repeatedly, such as process filters and sentinels.
     85 The `aio-chain` macro works in conjunction.
     86 
     87 ## Awaitable functions
     88 
     89 Here are some useful promise-returning — i.e. awaitable — functions
     90 defined by this package.
     91 
     92 ```el
     93 (aio-sleep seconds &optional result)
     94 ;; Return a promise that is resolved after SECONDS with RESULT.
     95 
     96 (aio-idle seconds &optional result)
     97 ;; Return a promise that is resolved after idle SECONDS with RESULT.
     98 
     99 (aio-url-retrieve url &optional silent inhibit-cookies)
    100 ;; Wraps `url-retrieve' in a promise.
    101 
    102 (aio-all promises)
    103 ;; Return a promise that resolves when all PROMISES are resolved."
    104 ```
    105 
    106 ## Select API
    107 
    108 This package includes a select()-like, level-triggered API for waiting
    109 on multiple promises at once. Create a "select" object, add promises
    110 to it, and await on it. Resolved and returned promises are
    111 automatically removed, and the "select" object can be reused.
    112 
    113 ```el
    114 (aio-make-select &optional promises)
    115 ;; Create a new `aio-select' object for waiting on multiple promises.
    116 
    117 (aio-select-add select promise)
    118 ;; Add PROMISE to the set of promises in SELECT.
    119 
    120 (aio-select-remove select promise)
    121 ;; Remove PROMISE form the set of promises in SELECT.
    122 
    123 (aio-select-promises select)
    124 ;; Return a list of promises in SELECT.
    125 
    126 (aio-select select)
    127 ;; Return a promise that resolves when any promise in SELECT resolves.
    128 ```
    129 
    130 For example, here's an implementation of sleep sort:
    131 
    132 ```el
    133 (aio-defun sleep-sort (values)
    134   (let* ((promises (mapcar (lambda (v) (aio-sleep v v)) values))
    135          (select (aio-make-select promises)))
    136     (cl-loop repeat (length promises)
    137              for next = (aio-await (aio-select select))
    138              collect (aio-await next))))
    139 ```
    140 
    141 ## Semaphore API
    142 
    143 Semaphores work just as they would as a thread synchronization
    144 primitive. There's an internal counter that cannot drop below zero,
    145 and `aio-sem-wait` is an awaitable function that may block the
    146 asynchronous function until another asynchronous function calls
    147 `aio-sem-post`. Blocked functions wait in a FIFO queue and are awoken
    148 in the same order that they awaited.
    149 
    150 ```el
    151 (aio-sem init)
    152 ;; Create a new semaphore with initial value INIT.
    153 
    154 (aio-sem-post sem)
    155 ;; Increment the value of SEM.
    156 
    157 (aio-sem-wait sem)
    158 ;; Decrement the value of SEM.
    159 ```
    160 
    161 This can be used to create a work queue. For example, here's a
    162 configurable download queue for `url-retrieve`:
    163 
    164 ```el
    165 (defun fetch (url-list max-parallel callback)
    166   (let ((sem (aio-sem max-parallel)))
    167     (dolist (url url-list)
    168       (aio-with-async
    169         (aio-await (aio-sem-wait sem))
    170         (cl-destructuring-bind (status . buffer)
    171             (aio-await (aio-url-retrieve url))
    172           (aio-sem-post sem)
    173           (funcall callback
    174                    (with-current-buffer buffer
    175                      (prog1 (buffer-string)
    176                        (kill-buffer)))))))))
    177 ```
    178 
    179 
    180 [asyncio]: https://docs.python.org/3/library/asyncio.html
    181 [melpa]: https://melpa.org/#/aio
    182 [post]: https://nullprogram.com/blog/2019/03/10/