Examples
Some limited examples are given in the documentation, but more in-depth example usages are a great way to learn. This section will give some basic usage examples to get you started.
An echo server
(defun my-echo-server ()
(format t "Starting server.~%")
(as:tcp-server nil 9003 ; nil is "0.0.0.0"
(lambda (socket data)
;; echo the data back into the socket
(as:write-socket-data socket data))
(lambda (err) (format t "listener event: ~a~%" err)))
;; catch sigint
(as:signal-handler 2 (lambda (sig)
(declare (ignore sig))
(as:exit-event-loop))))
(as:start-event-loop #'my-echo-server)
This listens to “0.0.0.0” on port 9003. When any data becomes available on the server, it immediately echos that data back to the connecting client. This goes on ad infinitum until the server recieves a SIGINT signal, then it forcibly exits the event loop.
Simple multi-dns lookup example
Let’s make a bunch of DNS queries in parallel and return the result once they’re finished.
(defpackage :dns-multi
(:use :cl :cl-async-future))
(in-package :dns-multi)
(defun get-all-dns (host-list)
"Lookup IP addresses for all the hosts in the given list. Returns a future
that's finished with the entries once they all finish (lookup happens in
parallel)."
(let* ((future (make-future))
(result nil)
(finished-count 0)
(finish-fn (lambda (addr fam)
(push (list :address addr :family fam) result)
(incf finished-count)
(when (<= (length host-list) finished-count)
(finish future result)))))
(dolist (host host-list)
(as:dns-lookup host
finish-fn
(lambda (ev)
(signal-error future ev)
(incf finished-count)
(when (<= (length host-list) finished-count)
(finish future result)))))
future))
(defun do-lookup ()
(alet ((result (get-all-dns '("www.google.com"
"musio.com"
"thievesco.com"
"yahoo.com"
"msg.com"
"cnn.com"))))
(format t "Result: ~s~%" result)))
(as:start-event-loop #'do-lookup :catch-app-errors t)
So we create a cl-async future, run all of our DNS queries in parallel (making sure to count them as finished when they succeed/fail), and once they have all finished, we finish the returned future with the end result.
The app can then bind to the result (using any of the future macros) and display the result. Notice how quickly it returns because the lookups are happening all at the same time.
Using futures (a simple driver example)
We’re going to create a simple client for an imaginary HTTP server that returns information on a user.
(defpackage :driver-test
(:use :cl :cl-async-future))
(in-package :driver-test)
(define-condition server-error (error)
((status :initarg :code :accessor server-error-status :initform nil)))
;; our driver function
(defun get-user-from-server (id)
"Spawn an HTTP request which grabs the given user by id. When the response
comes in, finish the returned future with the parsed JSON of the response."
(let ((future (make-future)))
(as:http-client (format nil "http://www.test-server.com/users/~a" id)
(lambda (status headers body)
(if (<= 200 status 299)
;; success. note we finish the future with multiple values (parsed json + status code)
(finish future (yason:parse body) status)
;; error code, signal error
(signal-error future (make-instance 'server-error :code status))))
(lambda (event)
(let ((event-type (type-of event)))
;; ignore info events (careful, event-error extends event-info)
(when (or (not (subtypep event-type 'as:event-info))
(subtypep event-type 'as:event-error))
;; forward any errors to the future, to be caught with future-handler-case
(signal-error future event))))
:timeout 5)
future))
;; our app function
(defun my-get-user (id)
(future-handler-case
;; bind multiple values to the future returned from get-user-from-server
(multiple-future-bind (user-data status)
(get-user-from-server id) ; make the request
(format t "Got user: ~a~%" (gethash "name" user-data))
;; "return" the status
status)
(server-error (e)
(format t "Error while getting user: status code (~a)~%" (server-error-status e)))
(t (e)
(format t "Got general error while looking up user: ~a~%" e))))
(as:start-event-loop (lambda () (my-get-user 17)) :catch-app-errors t)
This is an example of a very simple driver built on top of futures. For a real
driver, the get-user-from-server
would most likely be a lot more general and
would be able to process a number of different driver commands, but for this
example it works fine the way it is.
So what happens is get-user-from-server
spawns an HTTP request and returns a
future. The future is bound via multiple-future-bind
, whos body fires once the
future is finished with the response.
In the case of an error, future-handler-case
hooks into the future’s error
mechanism and catches any errors triggered via signal-error
. Thus, the markup
can be surprisingly like normal lisp but everything operates asynchronously. The
only difference is that the my-get-user
function will not return status
as
it would in normal lisp, but instead will return a future that will finish with
the value of status
once the body of multiple-future-bind
returns. So if we
want the value of status in the top-level form, we can do:
(as:start-event-loop
(lambda ()
(alet ((status (my-get-user 17)))
(format t "final status: ~a~%" status)))
:catch-app-errors t)
This is very close to normal lisp syntax, except that anything that returns a future must be wrapped in attach or some kind of future syntax macro to work as expected.
This is the standard way to implement drivers in cl-async. Read more on futures to get an understanding of what’s going on.
Github examples
See the examples on github, which provide some more advanced code samples.