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
;; 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)))))
new Promise ((resolve, reject) => {
resolve("ok");
});
(promise (lambda (resolve reject)
(funcall resolve "ok")))
;; also
(promise* (resolve reject)
(resolve "ok"))
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([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"))))
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"))