用异步调用覆盖函数值

Override function value with asynchronous call

如何在异步调用期间使用 cl-letf 或类似方法覆盖符号的函数值?我想在调用 start-processstart-process-shell-command 后停止显示缓冲区,而是取回一个字符串。

这是一个简化示例,其中绑定 display-buffer 适用于同步版本,但不适用于异步版本。此外,我已将词法绑定设置为 true。

(defun tst-fun-sync (url)
  (call-process "wget" nil "*wget*" nil url "-O" "-")
  (with-current-buffer "*wget*"
    (display-buffer (current-buffer))))

(defun tst-fun-async (url)
  (set-process-sentinel
   (start-process "wget" "*wget*" "wget" url "-O" "-")
   #'(lambda (p _m)
       (when (zerop (process-exit-status p))
         (with-current-buffer (process-buffer p)
           (display-buffer (current-buffer)))))))

(defun tst-fun-no-display (fun &rest args)
  (cl-letf (((symbol-function 'display-buffer)
             #'(lambda (&rest _ignored)
                 (message "%s" (buffer-string)))))
    (apply fun args)))

;; The first has desired result, but not the second
;; (tst-fun-no-display 'tst-fun-sync "http://www.whosebug.com")
;; (tst-fun-no-display 'tst-fun-async "http://www.whosebug.com")

让我们定义一个临时重新绑定的宏set-process-sentinel,以便可以用包装函数装饰哨兵函数。

(defmacro with-sentinel-wrapper (wrapper-fn &rest body)
  (let ((sps (gensym))
        (proc (gensym))
        (fn (gensym)))
    `(let ((,sps (symbol-function 'set-process-sentinel)))
       (cl-letf (((symbol-function 'set-process-sentinel)
                  (lambda (,proc ,fn)
                    (funcall ,sps ,proc (funcall ,wrapper-fn ,fn)))))
                ,@body))))

包装器可以通过建立任何有用的动态绑定来更改调用哨兵的动态上下文。在这里,我重复使用您的 cl-letf 来更改显示内容:

(with-sentinel-wrapper (lambda (fn)
                        (lexical-let ((fun fn)) 
                          (lambda (p m)
                            (cl-letf (((symbol-function 'display-buffer)
                                       #'(lambda (&rest _ignored)
                                           (message "%s" (buffer-string)))))
                              (funcall fun p m)))))
  (tst-fun-async "http://www.whosebug.com"))

现在,如果您不确定异步进程是否实际调用了 set-process-sentinel,您可能需要破解其他函数。