5

How can I pass a stringvariable in to this function, so that instead of printing "This is a test", it prints "This is a value-of-variable"

(async-start
   ;; What to do in the child process
   (lambda ()
     (message "This is a test")
     (sleep-for 3)
     222)

   ;; What to do when it finishes
   (lambda (result)
     (message "Async process done, result should be 222: %s" result)))

Update: I tried the answer suggested by @Tobias. Didn't work. Here's my code

;; ~/prelude/personal.el
(eval-after-load 'elfeed-search
  '(define-key elfeed-search-mode-map "d" #'my/baa))

;; comment
(defun my/baa ()
  (interactive )
  (let ((var "value-of-variable"))


  ;;(message "here %s" (format-time-string "%d/%m/%Y %H:%M:%S" (current-time)))
  (async-start
   ;; What to do in the child process
   (lambda ()
     (message "This is a %s" var)
     (sleep-for 2)
     var)

   ;; What to do when it finishes
   (lambda (result)
     (message "%s Async process done, result should be 222: %s" (format-time-string "%d/%m/%Y %H:%M:%S" (current-time)) result))))

  (next-line)
  ))

To test it, I would place point/cursor on a elfeed RSS feed item, and press letter d. I see in the minibuffer:

error in process sentinel: Symbol’s value as variable is void: var

By the way, as you can see, I suck at elisp indentation. How can I tell emacs to fix the indentation of the above code block.

Here's some background on what I'm trying to achieve. I have a RSS feed that has a steady steam of new articles. The feed lacks some key information. Let's say the feed is about companies listed in NYSE, and I would like to see the current stock price of the feed item. Since this info is available via the web via a seperate curl call, my solution is to bind letter "d" to an elisp function "my/baa". This function is non blocking. It is passed a reference to the currently selected elfeed item. When the (async) function returns, it prepends the stock price value to the line in the elfeed-search mode buffer. This is done through the "meta attribute" mechanism provided by elfeed. basically, any feed item can have a meta information. so the async function adds a meta inf to the elfeed item, and then triggers a ui update/refresh. I can hold down the d key press, and this will trigger a bunch of async's to fetch multiple http requests to get multiple stock quotes. this is non blocking. when the function returns, the display will get updated. each finish of an async function will trigger a redraw of the elfeed-search buffer.

Update 2: Tyler said the comma and backtick is crucial, so I added them, but still get the same error. Here's my edited code:

;; comment
(eval-after-load 'elfeed-search
  '(define-key elfeed-search-mode-map "d" #'my/baa))

;; comment
(defun my/baa ()
  (interactive)
  (let ((var "value-of-variable"))

  (async-start
   ;; What to do in the child process
   `(lambda ()
     (sleep-for 3) ;; future curl call
     ,(format "This is a %s" var))

   ;; What to do when it finishes
   (lambda (result)
     (message "Async process done, result should be something: %s"
              result))))

  (next-line))
6
  • 1
    What's your use case? The first lambda is sent as text to another Emacs process, so you can't magically pass current Emacs's state, you need to embed everything in that lambda.
    – xuchunyang
    Commented Jul 3, 2018 at 10:02
  • @xuchunyang A closure's print syntax would include captured variable values though, so maybe it could "magically" work.
    – npostavs
    Commented Jul 3, 2018 at 11:03
  • I don't see anything approximating "This is a value-of-variable" or even "This is a <value-of-variable>" in your code, so it's not even clear to me what behavior you are looking for. A wild guess is that this is somehow a duplicate of this: emacs.stackexchange.com/q/7481/105.
    – Drew
    Commented Jul 3, 2018 at 13:47
  • You didn't actually use @Tobias answer; he used a back-quote expression, which you assumed was a typo. You broke it by removing the back quote and comma.
    – Tyler
    Commented Jul 4, 2018 at 17:08
  • Copying your edited code and pasting it into Emacs, it works as expected. Maybe you forgot to re-evaluate the function definition?
    – Tyler
    Commented Jul 4, 2018 at 17:44

1 Answer 1

7

The following lisp code demonstrates how the value of a variable var can be injected into the lambda used as START-FUNC in (async-start START-FUNC &optional FINISH-FUNC). I changed the lambda START-FUNC with the help of a back-quote expression such that it returns a string modified with the value of the variable. Since the return value of START-FUNC is printed by FINISH-FUNC it is easier to check that the injection is successful.

(let ((var "value-of-variable"))
  (async-start
   ;; What to do in the child process
   `(lambda ()
      (sleep-for 3)
      ,(format "This is a %s" var))

       ;; What to do when it finishes
       (lambda (result)
         (message "Async process done, result should be 222: %s"
                  result))))

Note that (format "This is a %s" var) is run, i.e., the string "This is a value-of-variable" is constructed) before the lambda is used. Therefore, this is not some kind of interprocess-communication where thread safety might be an issue.

Note also that this is quite the intended way to inject variable values into async-start since the doc string of async-inject-variables gives an example that works in that way:

(async-inject-variables INCLUDE-REGEXP &optional PREDICATE EXCLUDE-REGEXP)

Return a ‘setq’ form that replicates part of the calling environment. It sets the value for every variable matching INCLUDE-REGEXP and also PREDICATE. It will not perform injection for any variable matching EXCLUDE-REGEXP (if present). It is intended to be used as follows:

(async-start
   ‘(lambda ()
      (require ’smtpmail)
      (with-temp-buffer
        (insert ,(buffer-substring-no-properties (point-min) (point-max)))
        ;; Pass in the variable environment for smtpmail
        ,(async-inject-variables "\‘\(smtpmail\|\(user-\)?mail\)-")
        (smtpmail-send-it)))
   ’ignore)

A note on interprocess communication with the async package:

In spite of the fact that async.el defines a function async-send I assume that the package is not really fit for interprocess communication.

There exists a test case async-test-5 for async-send.

I modified that test case a bit to make debugging easier:

(defun async-test-5 ()
  (interactive)
  (message "Starting async-test-5...")
  (let ((proc
         (async-start
          ;; What to do in the child process
          (lambda ()
        (with-temp-buffer
          (insert (format "Value of `async-callback': %S\n" async-callback))
          (async-send :hello "world")
          ;; wait for messages
          (while (let* ((msg (async-receive))
                (str (plist-get msg :goodbye)))
               (insert (format "Child got message: %s\n" str))
               (sleep-for 1)
               (null (string-equal str "goodbye"))))
          (buffer-string)))

          ;; What to do when it finishes
          (lambda (result)
            (if (async-message-p result)
                (message "Got hello from child process: %s"
                         (plist-get result :hello))
              (message "Async process done, result: %s"
                       result))))))
    (setq async-message-from-child nil)
    (async-send proc :goodbye "everyone")
    (sleep-for 1)
    (async-send proc :goodbye "just for you")
    (sleep-for 1)
    (async-send proc :goodbye "goodbye"))
  (message "Starting async-test-5...done"))

Running async-test-5 interactively outputs following message:

Async process done, result: Value of `async-callback': nil
Child got message: everyone
Child got message: just for you
Child got message: goodbye

As one sees one can send messages from the mother process to the child but sending messages from the child to the mother process (the (async-send :hello "world") thing) fails.

async-send uses the value of async-callback in the child and that variable is never set in the child. The output string Value of 'async-callback': nil proves that.

For completeness the code of async-send in version 1.9 of async.el:

(defun async-send (&rest args)
  "Send the given messages to the asychronous Emacs PROCESS."
  (let ((args (append args '(:async-message t))))
    (if async-in-child-emacs
        (if async-callback
            (funcall async-callback args))
      (async--transmit-sexp (car args) (list 'quote (cdr args))))))
6
  • is this thread safe? at any given moment I would have several asyncs running. Each async thread would know it's "work to be done" through the var variable. Commented Jul 3, 2018 at 17:21
  • @american-ninja-warrior I extended the answer. The extended answer also explains why thread safety is not an issue. Maybe, async-start is not as sophisticated as you think.
    – Tobias
    Commented Jul 4, 2018 at 8:03
  • is there a typo in your answer, the comma in " (sleep-for 3) ," Commented Jul 4, 2018 at 16:52
  • @american-ninja-warrior Look up backquote and unquote.
    – wasamasa
    Commented Jul 4, 2018 at 17:05
  • restarted emacs, and it works Commented Jul 5, 2018 at 1:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.