Skip to content

Instantly share code, notes, and snippets.

@vbuaraujo
Last active January 10, 2017 00:00
Show Gist options
  • Save vbuaraujo/a51eea7bcbcb23bfb9667888758b73cb to your computer and use it in GitHub Desktop.
Save vbuaraujo/a51eea7bcbcb23bfb9667888758b73cb to your computer and use it in GitHub Desktop.
class Promise(object):
def __init__(self, worker=None):
# A promise may be 'pending' or fulfilled. If it's fulfilled, then it
# may be 'resolved' or 'rejected'.
self.status = 'pending'
# After it's fulfilled, it has a value.
self.value = None
# Before it's fulfilled, computations waiting on the value of the
# promise are queued for execution when it gets fulfilled (either
# resolved or rejected).
self.callback_queue = {'resolved': [], 'rejected': []}
# Call the worker function passing methods which, when called,
# resolve or reject the created promise.
if worker:
worker(self._resolve, self._reject)
def _fulfill(self, status, val):
"""Fulfill the promise with either 'resolved' or 'rejected' `status`,
and value `val`. Run all the queued callbacks for the given status.
"""
if self.status != 'pending':
print "Oops, promise already fulfilled. Doing nothing!"
return
self.status = status
self.value = val
for callback in self.callback_queue[status]:
callback(val)
self.callback_queue = None # this shall not be used again
def _resolve(self, val):
self._fulfill('resolved', val)
def _reject(self, val):
self._fulfill('rejected', val)
def queue_callback(self, status, callback):
"""Enqueue callback to be run when the promise gets fulfilled with status
`status`. If the promise is already fulfilled with that status, run the
callback right away.
"""
if self.status == 'pending':
self.callback_queue[status].append(callback)
elif self.status == status:
callback(self.value)
else:
pass
# Now comes the tricky part.
def then(self, callback):
"""`then` does the following:
- It creates and returns a new promise. (Now we have two problems,
I mean, promises: the `self` promise, and the newly created one.)
- The `callback` function will only be called when and if the `self`
promise gets resolved (fulfilled with 'resolved' status).
- When `callback` is eventually called, whatever value it returns, the
new promise *resolves* with that value.
"""
new_promise = Promise()
def chained_resolve_callback(val):
# Run callback with the result of the `self` promise once it
# fulfills, and then resolve the `new_promise` with the result of
# the callback.
newval = callback(val)
new_promise._resolve(newval)
self.queue_callback('resolved', chained_resolve_callback)
return new_promise
### Example.
# Ideally we would have something asynchronous resolve the promise (as is the
# case when we use $.ajax), but here we will simply make a promise worker which
# drops its own 'resolve' method in a global variable so we can resolve the
# promise later.
promise_resolver = None
def example_worker(resolve, reject):
global promise_resolver
promise_resolver = resolve
p1 = Promise(example_worker)
# Let's make a callback to be called when p1 resolves, which takes whatever
# value it got and squares it. Then let's hang it on `p1` with `then`. The
# result is a second promise whose callback will run when `p1` resolves, and
# will itself resolve with the square of what `p1` resolved with.
def squarer_callback(val):
return val * val
p2 = p1.then(squarer_callback)
# Now let's hang *another* callback on `p2`, which will run whenever `p2`
# resolves and print the value it got resolved with.
def printer_callback(val):
print val
p3 = p2.then(printer_callback)
# Finally, we call p1's resolver, and watch the magic happen.
# p1 will resolve with 3, p2 will resolve with 9, and p3 will print it.
promise_resolver(3)
### Other details about JavaScript promises not implemented here:
#
# If a promise worker/callback raises an exception, the promise *rejects* with
# the exception value.
#
# If a promise p1 resolves with *another* promise (e.g., p1's worker/callback
# calls resolve(p2), where p2 is another promise), then p1 fulfills the same way
# as p2, i.e., if p2 resolves, then p1 resolves, and if p2 rejects, then p1
# rejects, with the same value. `p1` basically becomes a repeater of whatever p2
# fulfills with.
#
# Also, I forgot to implement `catch`.
#
### Other resources:
#
# I first learned about promises here:
#
# https://qntm.org/files/promise/promise.html
#
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment