Promises
########
For the reference see: :doc:`/reference/promise`
*AnPyLar* ships with its own version of *Promises*. The standard Python library
has *Futures* (the *asyncio* one) but *Promises* go simply a step
beyond. *Promises* are built on top a slighty (yet compatbible) modified
version of the *Futures* to allow setting anything as an error, rather than
just a *Exception*.
The implementation is made following the guidelines of *Promises/A+* (or
Promises-Aplus)
- `Web Site `_
- `Specification Repo `_
You may also check the documentation from *MDN Web Docs*
- `Promise@MD
`_
Quick introduction
******************
A *Promise* is an object that takes an *executor* (a *callable* in Python
slang) which will be executed by the promise during construction. The
*executor* will ideally start an asynchronous operation (like downloading a web
page in the background)
The *executor* is invoked with *callables*
- The ``resolve`` function which the *executor* has to invoke if the
operation succeeded.
- The ``reject`` function which the *executor* has to invoke if the operation
failed.
When the *Promise* has been created, one can await *resolution* or *rejection*
by:
- Invoking ``then(callable)`` for resolution.
The *callable* has to accept 1 parameter which will contain the *result*
that led to the resolution of the promise.
- Invoking ``catch(callable)`` for rejection.
The *callable* has to accept 1 parameter which will contain the *error*
that led to the rejection.
These can be chained and not once ... but actually as many times as wished.
Let's go for a practical example:
.. code-block:: python
from anpylar import Promise, call_delayed
def executor(resolve, reject):
call_delayed(1000, lambda: resolve(1))
mypromise = Promise(executor) \
.then(lambda x: x * 2) \
.then(lambda x: x * 3) \
.then(print)
As you may expect this will print the following (after 1000ms, i.e.: 1s) in the
developer console::
6
.. note::
You can test this simply script with ``anpylar-serve`` without creating a
complicated structure by placing the contents in a file ``index.py`` and
doing::
anpylar-serve --auto-serve index.py
We have chained several operations and they have been executed. Let's see what
would happen if the promise ended up in rejection.
.. code-block:: python
from anpylar import Promise, call_delayed
def executor(resolve, reject):
call_delayed(1000, lambda: reject('Blistering Barnacles!'))
mypromise = Promise(executor) \
.then(lambda x: x * 2) \
.then(lambda x: x * 3) \
.then(print) \
.catch(lambda x: print('Error:', x))
Which prints::
Error: Blistering Barnacles!
A more real example
*******************
So far we have only delayed the execution of call with ``call_delayed``, but we
can do some real world job, for example:
.. code-block:: python
from anpylar import Promise, Http
def executor(resolve, reject):
def _resolver(resp):
resolve(resp[0:min(50, len(resp))]) # 1st 50 chars of the answer
def _rejecter(error):
reject(error)
Http().get('http://127.0.0.1:2222/index.html') \
.catch_exception(lambda x: None if _rejecter(x) else None) \
.filter(lambda x: x is not None) \
.subscribe(_resolver)
Promise(executor) \
.then(lambda x: print('Promise.then:', x)) \
.catch(lambda x: print('Promise.catch: Error happened', x))
If you run this, the console will show the following::
Promise.then:
The Title
Let's change a line to break our code by setting the port to ``2223``::
Http().get('http://127.0.0.1:2223/index.html') \
And the console will now say::
GET http://127.0.0.1:2223/index.html net::ERR_CONNECTION_REFUSED
Promise.catch: Error happened
The ```GET ...`` error message is from the browser. Our ``catch`` method was
invoked and we know an error happened. Meanwhile, the ``then`` code was left
alone.
The real code now
*****************
*Observables* can be easily turned into *Promises* ... you don't have to do the
work yourself.
.. code-block:: python
from anpylar import Http
Http().get('http://127.0.0.1:2222/index.html') \
.to_promise() \
.then(lambda x: print('Promise.then:', x)) \
.catch(lambda x: print('Promise.catch: Error happened', x))
The ``to_promise`` operator turns the *Observable* into a *Promise* and forces
an internal subscription to make sure things reach either ``then`` or
``catch``. Play with it ...