dotemacs

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

README.md (5471B)


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