Index: mojo/public/python/mojo/bindings/promise.py |
diff --git a/mojo/public/python/mojo/bindings/promise.py b/mojo/public/python/mojo/bindings/promise.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c5d7d7cb641945e14af53d1b1d5e681db57d21b2 |
--- /dev/null |
+++ b/mojo/public/python/mojo/bindings/promise.py |
@@ -0,0 +1,188 @@ |
+# Copyright 2014 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+""" |
+Promise used by the python bindings. |
+ |
+The API is following the ECMAScript 6 API for promises. |
+""" |
+ |
+ |
+class Promise(object): |
+ """The promise object.""" |
+ |
+ STATE_PENDING = 0 |
+ STATE_FULLFILLED = 1 |
+ STATE_REJECTED = 2 |
+ STATE_BOUND = 3 |
+ |
+ def __init__(self, generator_function): |
+ """ |
+ Constructor. |
+ |
+ Args: |
+ generator_function: A function taking 2 arguments: resolve and reject. |
+ When |resolve| is called, the promise is fullfilled with the given value. |
+ When |reject| is called, the promise is rejected with the given value. |
+ A promise can only be resolved or rejected once, all following calls will |
+ have no effect. |
+ """ |
+ self._onCatched = [] |
+ self._onFulfilled = [] |
+ self._onRejected = [] |
+ self._state = Promise.STATE_PENDING |
+ self._result = None |
+ if generator_function: |
+ generator_function(self._Resolve, self._Reject) |
+ |
+ @staticmethod |
+ def Resolve(value): |
+ """ |
+ If value is a promise, make a promise that have the same behavior as value, |
+ otherwise make a promise that fulfills to value. |
+ """ |
+ if isinstance(value, Promise): |
+ return value |
+ return Promise(lambda x, y: x(value)) |
+ |
+ @staticmethod |
+ def Reject(reason): |
+ "Make a promise that rejects to reason.""" |
+ return Promise(lambda x, y: y(reason)) |
+ |
+ @staticmethod |
+ def All(*iterable): |
+ """ |
+ Make a promise that fulfills when every item in the array fulfills, and |
+ rejects if (and when) any item rejects. Each array item is passed to |
+ Promise.resolve, so the array can be a mixture of promise-like objects and |
+ other objects. The fulfillment value is an array (in order) of fulfillment |
+ values. The rejection value is the first rejection value. |
+ """ |
+ def GeneratorFunction(resolve, reject): |
+ state = { |
+ 'rejected': False, |
+ 'nb_resolved': 0, |
+ } |
+ promises = [Promise.Resolve(x) for x in iterable] |
+ results = [None for x in promises] |
+ def OnFullfilled(i): |
+ def OnFullfilled(res): |
+ if state['rejected']: |
+ return |
+ results[i] = res |
+ state['nb_resolved'] = state['nb_resolved'] + 1 |
+ if state['nb_resolved'] == len(results): |
+ resolve(results) |
+ return OnFullfilled |
+ def OnRejected(reason): |
+ if state['rejected']: |
+ return |
+ state['rejected'] = True |
+ reject(reason) |
+ |
+ for (i, promise) in enumerate(promises): |
+ promise.Then(OnFullfilled(i), OnRejected) |
+ return Promise(GeneratorFunction) |
+ |
+ @staticmethod |
+ def Race(*iterable): |
+ """ |
+ Make a Promise that fulfills as soon as any item fulfills, or rejects as |
+ soon as any item rejects, whichever happens first. |
+ """ |
+ def GeneratorFunction(resolve, reject): |
+ state = { |
+ 'ended': False |
+ } |
+ def OnEvent(callback): |
+ def OnEvent(res): |
+ if state['ended']: |
+ return |
+ state['ended'] = True |
+ callback(res) |
+ return OnEvent |
+ for promise in [Promise.Resolve(x) for x in iterable]: |
+ promise.Then(OnEvent(resolve), OnEvent(reject)) |
+ return Promise(GeneratorFunction) |
+ |
+ @property |
+ def state(self): |
+ if isinstance(self._result, Promise): |
+ return self._result.state |
+ return self._state |
+ |
+ def Then(self, onFullfilled=None, onRejected=None): |
+ """ |
+ onFulfilled is called when/if this promise resolves. onRejected is called |
+ when/if this promise rejects. Both are optional, if either/both are omitted |
+ the next onFulfilled/onRejected in the chain is called. Both callbacks have |
+ a single parameter, the fulfillment value or rejection reason. |Then| |
+ returns a new promise equivalent to the value you return from |
+ onFulfilled/onRejected after being passed through Resolve. If an |
+ error is thrown in the callback, the returned promise rejects with that |
+ error. |
+ """ |
+ if isinstance(self._result, Promise): |
+ return self._result.Then(onFullfilled, onRejected) |
+ def GeneratorFunction(resolve, reject): |
+ if self._state == Promise.STATE_PENDING: |
+ self._onFulfilled.append(_Delegate(resolve, reject, onFullfilled)) |
+ self._onRejected.append(_Delegate(reject, reject, onRejected)) |
+ if self._state == self.STATE_FULLFILLED: |
+ _Delegate(resolve, reject, onFullfilled)(self._result) |
+ if self._state == self.STATE_REJECTED: |
+ recover = reject |
+ if onRejected: |
+ recover = resolve |
+ _Delegate(recover, reject, onRejected)(self._result) |
+ return Promise(GeneratorFunction) |
+ |
+ def Catch(self, onCatched): |
+ """Equivalent to |Then(None, onCatched)|""" |
+ return self.Then(None, onCatched) |
+ |
+ def _Resolve(self, value): |
+ if self.state != Promise.STATE_PENDING: |
+ return |
+ self._result = value |
+ if isinstance(value, Promise): |
+ self._state = Promise.STATE_BOUND |
+ self._result.Then(_IterateAction(self._onFulfilled), |
+ _IterateAction(self._onRejected)) |
+ return |
+ self._state = Promise.STATE_FULLFILLED |
+ for f in self._onFulfilled: |
+ f(value) |
+ self._onFulfilled = None |
+ self._onRejected = None |
+ |
+ def _Reject(self, reason): |
+ if self.state != Promise.STATE_PENDING: |
+ return |
+ self._result = reason |
+ self._state = Promise.STATE_REJECTED |
+ for f in self._onRejected: |
+ f(reason) |
+ self._onFulfilled = None |
+ self._onRejected = None |
+ |
+ |
+def _IterateAction(iterable): |
+ def _Run(x): |
+ for f in iterable: |
+ f(x) |
+ return _Run |
+ |
+ |
+def _Delegate(resolve, reject, action): |
+ def _Run(x): |
+ try: |
+ if action: |
+ resolve(action(x)) |
+ else: |
+ resolve(x) |
+ except Exception as e: |
+ reject(e) |
+ return _Run |