4

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))
american-ninja-warrior
  • 3,773
  • 2
  • 21
  • 40
  • 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 Jul 03 '18 at 10:02
  • @xuchunyang A closure's print syntax would include captured variable values though, so maybe it could "magically" work. – npostavs Jul 03 '18 at 11:03
  • I don't see anything approximating *"This is a value-of-variable"* or even *"This is a "* 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: https://emacs.stackexchange.com/q/7481/105. – Drew Jul 03 '18 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 Jul 04 '18 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 Jul 04 '18 at 17:44
  • restarted emacs, and it works – american-ninja-warrior Jul 05 '18 at 01:53

1 Answers1

5

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))))))
Tobias
  • 32,569
  • 1
  • 34
  • 75