Skip to content

jordonbiondo/promises.el

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

45 Commits
 
 
 
 
 
 
 
 

Repository files navigation

promises.el

Promises for Emacs

  • the API is very similar to the A+ promise API.
  • supports explicitly asynchronous promises powered by `async.el`
  • supports promisifying callback-based functions

Examples

;; prints 15
(let ((my-promise
       (promise
        (lambda (reject resolve)
          (funcall resolve 5)))))
  (regardless my-promise
    (lambda (err value)
      (print (+ value 10)))))

;; async mapping
(then (promise-all (mapcar
                    (lambda (v) (promise-async (lambda () (expt v 2))))
                    (number-sequence 0 10)))
  (lambda (values)
    (message "%S" values)))

;; using `chain' and special promise forms
;; square every number 0-10 asynchronously
;; then multiple each by 5 asynchronously
;; message the results when done
;; if an error occurs anytime during execution log the error
;;
;; Output: "Results: (0 5 20 45 80 125 180 245 320 405 500)"
(chain (promise-all (mapcar
                     (lambda (v) (promise-async* (expt v 2)))
                     (number-sequence 0 10)))
  (then* (values)
    (promise-all (mapcar
                  (lambda (v) (promise-async* (* v 5)))
                  values)))
  (then* (values)
    (message "Results: %S" values))
  (on-error* (err)
    (message "Oops: %S" err)))


;; promisifying a callback-based function
;; when the callback would normally be called,
;; the returned promise will resolve with the
;; return value of the promisified function
;; and the callback's args
(defun my-slow-multiplier (a b callback)
  "After three seconds run CALLBACK with
the result of multiplying A and B."
  (run-with-timer
   3
   nil
   (lambda () (funcall callback (* a b)))))

(defalias 'my-promise-multiplier (promisify 'my-slow-multiplier))

(regardless* (my-promise-multiplier 5 6) (err output)
  (destructuring-bind (return-value result) output
    (message "Got: %S" result)))



;; Real example: reading possible 'npm run' scripts from package.json,
;; asking user to select one, then running it.
(defun my-npm-run ()
  (interactive)
  (let ((package-json-file "path/to/package.json"))
    (chain (promise-async*
             (require 'json)
             (let ((json (json-read-file package-json-file)))
               (cdr (assoc 'scripts json))))
      (then* (scripts)
        (promise-later* (resolve reject)
          (resolve
           (ido-completing-read "npm run " (mapcar 'symbol-name (mapcar 'car scripts))))))
      (then* (script)
        (when script
          (compile (format "cd %s && npm run %s" "/my/project-dir/" script) t)))
      (on-error* (err)
        (print err)))))
;; Another useful example:

;; Below is a function that runs a shell command asynchronously
;; and returns a promise. The promise resolves with the output
;; of the shell command, or rejects with a list in the form:
;; (ERROR-STATUS OUTPUT).


(setq lexical-binding t)

(defun async-shell-command-to-string (command)
  (promise* (ok nope)
    (condition-case err
        (let* ((command-buffer (generate-new-buffer "*async-sc-promise*"))
               (proc (apply 'start-process "*async-sc-promise*" command-buffer
                            shell-file-name shell-command-switch (list command)))
               (sentinal-fn
                (lambda (proc status)
                  (unwind-protect
                      (condition-case err
                          (unless (process-live-p proc)
                            (let ((string (with-current-buffer command-buffer (buffer-string))))
                              (if (zerop (process-exit-status proc))
                                  (ok string)
                                (nope (list status string)))))
                        (error (nope err)))
                    (ignore-errors (kill-buffer command-buffer))))))
          (set-process-sentinel proc sentinal-fn))
      (error (nope err)))))


(chain (async-shell-command-to-string "bash -c 'ecxho hi'")
  (then* (val)
    (message "val %s" (string-trim val)))
  (on-error* (err)
    (message "status: %s, output: %s"
             (string-trim (car err))
             (string-trim (cadr err)))))

Javascript equivilants

A simple promise

new Promise ((resolve, reject) => {
    resolve("ok");
});
(promise (lambda (resolve reject)
           (funcall resolve "ok")))

;; also

(promise* (resolve reject)
  (resolve "ok"))

Chaining

findUser({name: "Bob"})
    .then((user) => {
        user.age++;
        return user.save()
    })
    .then((updatedUser) => {
        return updatedUser.age;
    })
    .catch((error) => {
        return Error("Could not increase age" + error);
    });
(chain (findUser (list :name "Bob"))
  (then* (user)
    (incf (user-age user))
    (user-save))
  (then* (update-user)
    (user-age user))
  (on-error* (err)
    (error "Could not increase age. %S" err)))

Promise.all

Promise
    .all([getFoo(), getBar(), 30])
    .then((values) => {
        var foo = values[0];
        var bar = values[1];
        var thirty = values[2];
        doSomething(foo, bar, thirty);
    }, (err) => {
        console.log("shucks");
    });
(chain (promise-all (list (get-foo) (get-bar) 30))
  (then
    (lambda (values)
      (destructuring-bind (foo bar thirty) values
        (do-somthing foo bar thirty)))
    (lambda (err)
      (message "shucks"))))

Notable differences with other promise apis:

Promise are not explicitely asynchronous or delayed. The body is executed immediately before `promise` exits.

;; In this scenario "a" is guaranteed to be messaged before "b"

(progn
  (promise
   (lambda (resolve reject)
     (funcall resolve (message "a"))))
  (message "b"))

About

promises for Emacs

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published