Open telotortium opened 3 years ago
emacs-aio looks promising - see https://nullprogram.com/blog/2019/03/10/. Unfortunately, you can't fully preserve stack traces in Emacs, but perhaps this will still make the code easier to read and not prone to stack overflows.
I've decided to go with emacs-aio - since it uses the Emacs generator framework, code written using this framework looks just like async/await code in many modern languages, so it will be a lot easier to read than others. I've decided to go with emacs-aio instead of emacs-async-await because it uses Emacs errors instead of a separate reject
callback for the error case, which I think it easier to read and preserves some semblance of a stack trace. I'm particularly impressed by the easy imperative code pattern:
(aio-defun org-gcal--get-event-aio (calendar-id event-id)
"Retrieves a Google Calendar event given a CALENDAR-ID and EVENT-ID.
Refreshes the access token if needed.
Returns an ‘aio-promise’ for a ‘request-response' object."
(let* ((a-token (org-gcal--get-access-token))
(response
(aio-await
(org-gcal--aio-request
(concat
(org-gcal-events-url calendar-id)
(concat "/" event-id))
:type "GET"
:headers
`(("Accept" . "application/json")
("Authorization" . ,(format "Bearer %s" a-token)))
:parser 'org-gcal--json-read)))
(_data (request-response-data response))
(status-code (request-response-status-code response))
(error-thrown (request-response-error-thrown response)))
(cond
;; If there is no network connectivity, the response will not
;; include a status code.
((eq status-code nil)
(org-gcal--notify
"Got Error"
"Could not contact remote service. Please check your network connectivity.")
(error "Network connectivity issue"))
((eq 401 (or (plist-get (plist-get (request-response-data response) :error) :code)
status-code))
(org-gcal--notify
"Received HTTP 401"
"OAuth token expired. Now trying to refresh token.")
(aio-await (org-gcal--refresh-token-aio))
(aio-await (org-gcal--get-event-aio calendar-id event-id)))
;; Generic error-handler meant to provide useful information about
;; failure cases not otherwise explicitly specified.
((not (eq error-thrown nil))
(org-gcal--notify
(concat "Status code: " (number-to-string status-code))
(pp-to-string error-thrown))
(error "Got error %S: %S" status-code error-thrown))
;; Fetch was successful.
(t response))))
(aio-defun org-gcal--refresh-token-aio ()
"Refresh OAuth access and return the new access token as a deferred object."
(let* ((response
(aio-await
(org-gcal--aio-request
org-gcal-token-url
:type "POST"
:data `(("client_id" . ,org-gcal-client-id)
("client_secret" . ,org-gcal-client-secret)
("refresh_token" . ,(org-gcal--get-refresh-token))
("grant_type" . "refresh_token"))
:parser 'org-gcal--json-read)))
(data (request-response-data response))
(status-code (request-response-status-code response))
(error-thrown (request-response-error-thrown response)))
(cond
((eq error-thrown nil)
(plist-put org-gcal-token-plist
:access_token
(plist-get data :access_token))
(org-gcal--save-sexp org-gcal-token-plist org-gcal-token-file)
(let ((token (plist-get org-gcal-token-plist :access_token)))
token))
(t
(error "Got error %S: %S" status-code error-thrown)))))
org-gcal--aio-request
can be found in #160, where I'm doing the aio
rewrite.
I want to accomplish several things with this:
deferred.el
code are generally useless, which makes it hard to debug issues in this library.deferred
chains ordeferred:loop
) there is no tail call elimination, so eventually you will stack overflow. I got around this by throwing and catching errors (seeorg-gcal--sync-buffer-inner
from #129), but this also makes the stack hard to follow.