-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathpromise-examples.el
459 lines (381 loc) · 16.7 KB
/
promise-examples.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
;;; promise-examples.el --- Examples using `promise.el'. -*- lexical-binding: t; -*-
;; Copyright (C) 2016-2017 chuntaro
;; Author: chuntaro <[email protected]>
;; URL: https://github.com/chuntaro/emacs-promise
;; Package-Requires: ((emacs "25") (async "1.9"))
;; Version: 1.0
;; Keywords: convenience
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; This file is examples using `promise.el'.
;;
;; To execute this, move the point after the last parenthesis of the following
;; Lisp code and press C-x C-e. (Launch the new Emacs and run (launcher))
;;
;; (start-process "emacs" nil (file-truename (expand-file-name invocation-name invocation-directory)) "-Q" "-f" "package-initialize" "-L" (concat default-directory "../") "-l" (buffer-file-name) "-f" "launcher")
;;; Code:
(require 'promise)
(require 'url-http)
(require 'xml)
(require 'dom)
(defun do-something ()
"Return `Promise' to resolve the value synchronously."
(promise-new (lambda (resolve _reject)
(let ((value 33))
(funcall resolve value)))))
(defun example1 ()
"Resolved Promise keeps returning the same value."
(let ((promise (do-something)))
(promise-then promise
(lambda (value)
(message "Got a value: %s" value)))
(promise-then promise
(lambda (value)
(message "Got the same value again: %s" value)))))
(defun example2 ()
"Promise chain."
(let ((promise (do-something)))
(setf promise (promise-then promise
(lambda (result)
(message "first result: %s" result)
88)))
(setf promise (promise-then promise
(lambda (second-result)
(message "second result: %s" second-result)
99)))
(setf promise (promise-then promise
(lambda (third-result)
(message "third result: %s" third-result))))))
(defun example3 ()
"Same result as `example2'.
`promise-chain' macro is a syntax sugar for easy writing."
(promise-chain (do-something)
(then (lambda (result)
(message "first result: %s" result)
88))
(then (lambda (second-result)
(message "second result: %s" second-result)
99))
(then (lambda (third-result)
(message "third result: %s" third-result)))))
(defun do-something-async (delay-sec value)
"Return `Promise' to resolve the value asynchronously."
(promise-new (lambda (resolve _reject)
(run-at-time delay-sec
nil
(lambda ()
(funcall resolve value))))))
(defun example4 ()
"All processes are asynchronous Promise chain."
(promise-chain (do-something-async 1 33)
(then (lambda (result)
(message "first result: %s" result)
(do-something-async 1 (* result 2))))
(then (lambda (second-result)
(message "second result: %s" second-result)
(do-something-async 1 (* second-result 2))))
(then (lambda (third-result)
(message "third result: %s" third-result)))))
(defvar a-dummy)
(defun example5 ()
"Catch the error."
(promise-chain (do-something-async 1 33)
(then (lambda (result)
(message "first result: %s" result)
(setf a-dummy (/ 1 0)))) ; An `(arith-error)' occurs here.
(then (lambda (second-result)
(message "second result: %s" second-result)
(do-something-async 1 (* second-result 2)))
(lambda (reason)
(message "catch the error: %s" reason)))))
(defun example6 ()
"Same result as `example5'."
(promise-chain (do-something-async 1 33)
(then (lambda (result)
(message "first result: %s" result)
(setf a-dummy (/ 1 0)))) ; An `(arith-error)' occurs here.
(then nil
(lambda (reason)
(message "catch the error: %s" reason)))))
(defun example7 ()
"Same result as `example6'. `promise-catch' is a syntax sugar."
(promise-chain (do-something-async 1 33)
(then (lambda (result)
(message "first result: %s" result)
(setf a-dummy (/ 1 0)))) ; An `(arith-error)' occurs here.
(promise-catch (lambda (reason)
(message "catch the error: %s" reason)))))
(defun example8 ()
"How to use `promise-race'."
(promise-chain (promise-race (vector (do-something-async 2 "2 seccods")
(do-something-async 1 "1 second")
(do-something-async 3 "3 secconds")))
(then (lambda (result)
(message "result: %s" result)))))
(defun timeout (time)
"Return `Promise' which times out after the specified time."
(promise-new (lambda (_resolve reject)
(run-at-time time
nil
(lambda ()
(funcall reject "time out"))))))
(defun example9 ()
"How to time out using `promise-race'."
(promise-chain (promise-race (vector (timeout 2)
(do-something-async 3 "3 seconds")))
(then (lambda (result)
(message "result: %s" result)))
(promise-catch (lambda (reason)
(message "promise-catch: %s" reason)))))
(defun example10 ()
"How to use `promise-all'."
(promise-chain (promise-all (vector (do-something-async 2 "2 seccods")
(do-something-async 1 "1 second")
(do-something-async 3 "3 secconds")))
(then (lambda (results)
(message "result[0]: %s" (aref results 0))
(message "result[1]: %s" (aref results 1))
(message "result[2]: %s" (aref results 2))))))
(defun do-randomthing-async ()
"Return `Promise' to resolve the random value asynchronously."
(promise-new (lambda (resolve _reject)
(run-at-time 1
nil
(lambda ()
(funcall resolve (random 100)))))))
(defun example11 ()
"Branching to `resolve' or `reject' depending on the result."
(promise-chain (do-randomthing-async)
(then (lambda (result)
(if (>= result 50)
(promise-resolve (format "enough (%d >= 50)" result))
(promise-reject (format "short (%d < 50)" result)))))
(then (lambda (result)
(message "result: %s" result)))
(promise-catch (lambda (reason)
(message "promise-catch: %s" reason)))))
;;
;; Example using `url-retrieve'
;;
(defun xml-retrieve (url) ; Same as `promise:xml-retrieve'
"Return `Promise' to resolve with XML object obtained by HTTP request."
(promise-new
(lambda (resolve reject)
(url-retrieve url
(lambda (status)
;; All errors are reliably captured and rejected with appropriate values.
(if (plist-get status :error)
(funcall reject (plist-get status :error))
(condition-case ex
(if (not (url-http-parse-headers))
(funcall reject (buffer-string))
(search-forward-regexp "\n\\s-*\n" nil t)
(funcall resolve (xml-parse-region)))
(error (funcall reject ex)))))))))
(defun get-text-first-tag (xml tag)
"Returns the first text that matches TAG in XML."
(decode-coding-string (dom-text (cl-first (dom-by-tag xml tag)))
'utf-8))
(defun get-short-text-first-tag (xml tag)
"Truncate the text obtained with `get-text-first-tag'."
(concat (truncate-string-to-width (get-text-first-tag xml tag) 64)
" ..."))
(defun wait-seconds (seconds fn &rest args) ; Same as `promise:run-at-time'
"Return `Promise' to execute the function after the specified time."
(promise-new (lambda (resolve _reject)
(run-at-time seconds
nil
(lambda ()
(funcall resolve (apply fn args)))))))
(defun example12 ()
"Example using `xml-retrieve'."
(let ((wikipedia-url (concat "https://en.wikipedia.org/w/api.php"
"?format=xml&action=query&prop=extracts"
"&exintro=&explaintext=&titles=")))
(promise-chain (promise-all
(vector
(xml-retrieve (concat wikipedia-url (url-encode-url "GNU")))
;; Request after 2 seconds for load reduction.
(wait-seconds 2
#'xml-retrieve
(concat wikipedia-url (url-encode-url "Emacs")))))
(then (lambda (xmls)
(message "%s" (get-short-text-first-tag (aref xmls 0) 'extract))
(message "%s" (get-short-text-first-tag (aref xmls 1) 'extract))))
(promise-catch (lambda (reason)
(message "promise-catch: %s" reason))))))
;;
;; Asynchronous Processes
;;
(defun make-grep-process (&rest args)
"Return Promise which invokes the process asynchronously
and resolves it in the output result."
(promise-new
(lambda (resolve reject)
(make-process :name "grep"
:buffer "*grep-result*"
:command (cl-list* "grep" args)
:sentinel (lambda (_process event)
(if (string= event "finished\n")
(with-current-buffer "*grep-result*"
(funcall resolve (buffer-string)))
(funcall reject event)))))))
(defun example13 ()
"An example using `make-process'."
(promise-chain (make-grep-process "make-process" "promise-examples.el")
(then (lambda (result)
(message "grep result:\n%s" result)))
(promise-catch (lambda (reason)
(message "promise-catch: %s" reason)))))
(defun example14 ()
"Same result as `example13'."
(promise-chain (promise:make-process-string
'("grep" "make-process" "promise-examples.el"))
(then (lambda (result)
(message "grep result:\n%s" result)))
(catch (lambda (reason)
(message "promise-catch: %s" reason)))))
(defun example15 ()
"An example when `make-process' returns an error."
(promise-chain (promise:make-process-string
'("grep" "string not in source \\ " "promise-examples.el"))
(then (lambda (result)
(message "grep result:\n%s" result)))
(promise-catch (lambda (reason)
(message "promise-catch: %s" reason)))))
(defun example16 ()
"Example using promise: async-start.
Get the 30000th value of Fibonacci number."
(promise-chain (promise:async-start (lambda ()
(require 'calc-ext)
(defmath fibonacci (n)
"Calculate n-th Fibonacci number."
(let ((a 1)
(b 0)
c
(k 2))
(while (<= k n)
(setq c b
b a
a (+ b c)
k (+ k 1)))
a))
(calc-eval "fibonacci(30000)")))
(then (lambda (result)
(message "fibonacci(30000) -> %s" result)))))
;;
;; Thenable
;;
;; This `emacs-promise' makes `thenable' an OBJECT whose `promise-then' is defined.
;; OBJECT must be created with `defstruct' or `defclass'.
;;
(cl-defstruct thenable
value)
(cl-defmethod promise-then ((this thenable) &optional resolve reject)
"The signature of this method must be the same."
(run-at-time 1 nil (lambda ()
(if (thenable-value this)
(funcall resolve (concat "[" (upcase (thenable-value this)) "]"))
(funcall reject "failed: thenable")))))
(defun example17 ()
"Thenable must be passed to `promise-resolve'."
(promise-chain (promise-resolve (make-thenable :value "This is `thenable'"))
(then (lambda (result)
(message "result: %s" result)))
(promise-catch (lambda (reason)
(message "promise-catch: %s" reason)))))
;;
;; Inheritance of Promise
;;
(defclass simple-logger (promise-class)
((call-count :accessor call-count :initform 0))
:documentation "Record the number of times `promise-then' was called.")
(cl-defmethod promise-then ((this simple-logger) &optional on-fulfilled on-rejected)
(let ((new-promise ; `promise-then' always returns a new promise."
(cl-call-next-method this
(lambda (result)
(message "%d: result: %s"
(1+ (call-count this))
result)
(funcall on-fulfilled result))
on-rejected)))
(setf (call-count new-promise) (1+ (call-count this)))
new-promise))
(defun example18 ()
(promise-chain (make-instance 'simple-logger
:fn (lambda (resolve _reject)
(let ((value 33))
(funcall resolve value))))
(then (lambda (result)
(* result 2)))
(then (lambda (second-result)
(setf a-dummy (/ 1 0)) ; An `(arith-error)' occurs here.
(* second-result 2)))
(then (lambda (third-result)
;; Do not reach
(message "third result: %s" third-result)))
;; In the `promise-chain', the `promise' variable is defined.
(message "* type-of promise: %s" (promise--type-of promise))
(message "* `promise-then' total call count: %d" (call-count promise))))
;;
;; Unhandled Rejections
;;
(defun example19 ()
"An example where Promise swallows an error."
(promise-chain (do-something-async 1 33)
(then (lambda (result)
(message "first result: %s" result)
(setf a-dummy (/ 1 0)))) ; An `(arith-error)' occurs here.
;; Oops! I forgot to capture the error!
;; Nothing is displayed except for the first result.
(then (lambda (second-result)
(message "second result: %s" second-result)))))
(require 'promise-rejection-tracking)
(defun example20 ()
"Example of `rejection-tracking'."
;; Enable `rejection-tracking'.
;; The option should always specify (all-rejections . t).
(promise-rejection-tracking-enable '((all-rejections . t)))
;; Since this has a penalty of execution speed,
;; it should be effective only during development.
(promise-chain (do-something-async 1 33)
(then (lambda (result)
(message "first result: %s" result)
(setf a-dummy (/ 1 0)))) ; An `(arith-error)' occurs here.
;; if rejection-tracking is enabled,
;; an error will be displayed in a few seconds!
(then (lambda (second-result)
(message "second result: %s" second-result)))))
;;
;; Launcher
;;
(defun launcher ()
"A launcher that runs each example."
(require 'ido)
(switch-to-buffer "*Messages*")
(setq inhibit-message t
scroll-conservatively 10000)
(let (nums)
(mapatoms
(lambda (x)
(when (fboundp x)
(let ((name (symbol-name x)))
(when (string-match "^example\\([0-9]+\\)$" name)
(push (match-string 1 name) nums))))))
(cl-callf cl-sort nums #'< :key #'string-to-number)
(cl-loop
(let* ((num (ido-completing-read "What number of examples do you run?: example"
nums))
(example (intern (concat "example" num))))
(message "***** example%s *****" num)
(funcall example)))))
;;; promise-examples.el ends here