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/