Git Product home page Git Product logo

emacs-aio's Introduction

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)))))))))

emacs-aio's People

Contributors

kiennq avatar phst avatar skeeto avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

emacs-aio's Issues

edebug support?

Hello,

Thanks for this package, it makes emacs async sane.

Is there a way to use edebug tho? All my attempts seems to fail, even when explicitely writing (edebug) in my forms.

Usage with shell command

I'm trying to use aio to run a shell command but my call seems to be blocking (cannot move cursor until stop is written). I imagine I'm doing something wrong but I'm not quite sure where:

(defun aio-shell (command)
  (let ((promise (aio-promise)))
    (prog1 promise
      (aio-resolve promise (lambda () (shell-command-to-string command))))))

(aio-defun aio-run (command)
  (interactive)
  (message "Start") (aio-await (aio-shell command)) (message "Stop"))

(aio-run "sleep 3")

Is that the right syntax?

"progn: Symbol’s value as variable is void: cps-state-terminal-1176" after M-x eval-buffer

Minimal example:

(require 'aio)

(setq lexical-binding t)

(aio-defun test ()
  1)

(aio-wait-for (test))

Strangely this program works when I eval everything one-by-one using C-x C-e. I'm wondering if there's something wrong with using eval-buffer with this library. Thanks for the help.

Emacs version:

GNU Emacs 26.3 (build 1, x86_64-pc-linux-gnu, GTK+ Version 3.22.30) of 2019-12-02

Document use of `aio-make-callback` and `aio-chain`

I would like to use emacs-aio for my package emacs-pubmed. The aim is to retrieve multiple urls in queue, i.e. while maintaining the order (see issue 3). Meanwhile, I have to respect a limit of requests/second.

I find this difficult to achieve with the semaphore API, and thought maybe the aio-make-callback function and aio-chain macro could be more appropriate. However, I don't quite understand how they work.

Could you provide an example?

Can `emacs-aio` support Emacs 25?

From the README it seems that emacs-aio is using Emacs 25 generator, but the requirement for it is Emacs 26.
Can emacs-aio works with Emacs 25?

Does not work in interactive spec

Hello,

(aio-defun foo (data)
  (interactive (aio-await foo-async))
  (message data))

That does not work. You need to use (interactive) (setq data (aio-await foo-async))

how to set timeouts with aio

i just gave this a library a go for a simple url-retrieve functionality.

i'm wondering how to set a timeout for it?

so far i just used code as in your example:

((let ((response (aio-await (aio-url-retrieve url))))
  do stuff w/ (cdr response)

i see there's aio-timeout and aio-idle but it's unclear to me how to use these to give up on such a request, if that's even what they can be used for? (since using aio in my code, sometimes a response comes in about 10 minutes late, longer after i have given up and re-run the command to fetch the url.

Comparison to emacs-async

Hi, I am interested in emacs-aio. I am using emacs-async, which is also used in magit. Can you please compare both package? What's the advantage of emacs-aio?

I noticed @tarsius stared aio, maybe he will use it instead of emacs-async in magit if aio is much better.

BTW, aio will be released on melpa or elpa?

Show correct arguments in documentation for `aio-defun` functions.

I'm working on a project using this library and it's great. The one problem is that in eldoc and other documentation, all of my aio-defun'd function show their argument list as (&rest args) which means I have to got to the definition to see how to call a function.

One solution I've found is to use set-advertised-calling-convention in the aio-defun macro.

What do you think?

(defmacro aio-defun (name arglist &rest body)
  "Like `aio-lambda' but gives the function a name like `defun'."
  (declare (indent defun)
           (doc-string 3)
           (debug (&define name lambda-list &rest sexp)))
  `(progn
     (defalias ',name (aio-lambda ,arglist ,@body))
     (function-put ',name 'aio-defun-p t)
     (set-advertised-calling-convention ',name ',arglist 0)))

Understanding aio-defun

Hello,

The following code works:

(defun aio-call-process (program &rest args)
  (let* ((process (apply #'start-file-process program "*aio-call-process*" program args))
         (promise (aio-promise)))
    (prog1
        promise
      (setf (process-sentinel process) (-partial #'aio-process-sentinel promise)))))

(defun aio-process-sentinel (promise proc status)
  "Sentinel that resolves the PROMISE using PROC and STATUS."
  (aio-resolve
   promise
   (lambda ()
     (with-current-buffer (process-buffer proc)
       (prog1
           (buffer-substring-no-properties (point-min) (point-max))
         (kill-buffer))))))

(aio-defun aio-run (command &rest args)
  (interactive)
  (message "Start")
  (message (aio-await (apply #'aio-call-process command args)))
  (message "Stop"))

(aio-run "echo" "123")

But if I just change (defun aio-call-process (program &rest args) to (aio-defun aio-call-process (program &rest args) then nothing works anymore. I'm confused, can you clarify why?

I thought aio-defun was to enable aio-await but that it was still somewhat of a normal function.

Also, apparently I'm supposed to use aio-make-callback to simplify the code above but honestly I have no clue how 😅

how to use with edebug

when i write aio functions, edebug doesn't work with them. it doesn't error when instrumenting them, but when the code runs, edebug doesn't run.

has anyone solved this issue and can explain how to fix?

Eager macro-expansion failure with `with-current-buffer`

Hello

In the following code, ok behaves properly but buggy creates an error.

(aio-defun ok ()
  (interactive)
  (let ((data (aio-await (aio-sleep 5 "yeah"))))
    (with-current-buffer "test"
      (insert data))))

(aio-defun buggy ()
  (interactive)
  (with-current-buffer "test"
    (insert (aio-await (aio-sleep 5 "yeah")))))

Eager macro-expansion failure: (error "special form (save-current-buffer (set-buffer \"test\") (insert (funcall (cps-internal-yield (aio-sleep 5 \"yeah\"))))) incorrect or not supported")

Assertion failed on emacs 26.3

So I'd like to use aio for a personal project on Emacs 26.3, but I cannot use aio-defun. I suppose I must be missing something: evaluating

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

Gives

Debugger entered--Lisp error: (cl-assertion-failed (lexical-binding nil))
  cl--assertion-failed(lexical-binding)
  #f(compiled-function (arglist &rest body) "Return a lambda generator.\n`iter-lambda' is to `iter-defun' as `lambda' is to `defun'." #<bytecode 0x44a9f0c1>)((a b) (aio-with-promise promise (condition-case error (aio-await (divide a b)) (arith-error :arith-error))))
  macroexpand((iter-lambda (a b) (aio-with-promise promise (condition-case error (aio-await (divide a b)) (arith-error :arith-error)))) nil)
  macroexp-macroexpand((iter-lambda (a b) (aio-with-promise promise (condition-case error (aio-await (divide a b)) (arith-error :arith-error)))) nil)
  macroexp--expand-all((iter-lambda (a b) (aio-with-promise promise (condition-case error (aio-await (divide a b)) (arith-error :arith-error)))))
  macroexp--all-forms((apply (iter-lambda (a b) (aio-with-promise promise (condition-case error (aio-await (divide a b)) (arith-error :arith-error)))) args) 1)
  #f(compiled-function (form func) #<bytecode 0x400a9353>)(((apply (iter-lambda (a b) (aio-with-promise promise (condition-case error (aio-await (divide a b)) (arith-error :arith-error)))) args)) apply)
  macroexp--expand-all((apply (iter-lambda (a b) (aio-with-promise promise (condition-case error (aio-await (divide a b)) (arith-error :arith-error)))) args))
  macroexp--all-forms((iter (apply (iter-lambda (a b) (aio-with-promise promise (condition-case error (aio-await (divide a b)) (arith-error :arith-error)))) args)) 1)
  macroexp--all-clauses(((promise (aio-promise)) (iter (apply (iter-lambda (a b) (aio-with-promise promise (condition-case error (aio-await ...) (arith-error :arith-error)))) args))) 1)
  #f(compiled-function (form body bindings fun) #<bytecode 0x400a9393>)(((let* ((promise (aio-promise)) (iter (apply (iter-lambda (a b) (aio-with-promise promise ...)) args))) (prog1 promise (aio--step iter promise nil)))) ((prog1 promise (aio--step iter promise nil))) ((promise (aio-promise)) (iter (apply (iter-lambda (a b) (aio-with-promise promise (condition-case error (aio-await ...) (arith-error :arith-error)))) args))) let*)
  macroexp--expand-all((let* ((promise (aio-promise)) (iter (apply (iter-lambda (a b) (aio-with-promise promise (condition-case error ... ...))) args))) (prog1 promise (aio--step iter promise nil))))
  macroexp--all-forms((lambda (&rest args) (let* ((promise (aio-promise)) (iter (apply (iter-lambda (a b) (aio-with-promise promise ...)) args))) (prog1 promise (aio--step iter promise nil)))) 2)
  macroexp--expand-all((aio-lambda (a b) (condition-case error (aio-await (divide a b)) (arith-error :arith-error))))
  macroexp--all-forms((defalias (quote divide-safe) (aio-lambda (a b) (condition-case error (aio-await (divide a b)) (arith-error :arith-error)))) 1)
  #f(compiled-function (form func) #<bytecode 0x400a9353>)(((defalias (quote divide-safe) (aio-lambda (a b) (condition-case error (aio-await (divide a b)) (arith-error :arith-error))))) defalias)
  macroexp--expand-all((aio-defun divide-safe (a b) (condition-case error (aio-await (divide a b)) (arith-error :arith-error))))
  macroexpand-all((aio-defun divide-safe (a b) (condition-case error (aio-await (divide a b)) (arith-error :arith-error))))
  eval-sexp-add-defvars((aio-defun divide-safe (a b) (condition-case error (aio-await (divide a b)) (arith-error :arith-error))))
  elisp--eval-last-sexp(nil)
  eval-last-sexp(nil)
  funcall-interactively(eval-last-sexp nil)
  call-interactively(eval-last-sexp nil nil)
  command-execute(eval-last-sexp)

aio-defun doesn't support declarations

For example, after

(aio-defun func ()
  (declare (obsolete ("foo"))))

C-h f func RET won't show that the function is obsolete. Applying declarations is done by the defun macro; probably aio-defun should wrap defun in the same way that iter-defun wraps defun.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.