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/