summaryrefslogtreecommitdiffstats
path: root/chrome/common/extensions/docs/server2/future.py
blob: 289761c43d301162fc76a469ea36e54f8a98e9eb (plain)
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
# Copyright (c) 2012 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.

import logging
import sys
import traceback

_no_value = object()


def _DefaultErrorHandler(error):
  raise error


def All(futures, except_pass=None, except_pass_log=False):
  '''Creates a Future which returns a list of results from each Future in
  |futures|.

  If any Future raises an error other than those in |except_pass| the returned
  Future will raise as well.

  If any Future raises an error in |except_pass| then None will be inserted as
  its result. If |except_pass_log| is True then the exception will be logged.
  '''
  def resolve():
    resolved = []
    for f in futures:
      try:
        resolved.append(f.Get())
      # "except None" will simply not catch any errors.
      except except_pass:
        if except_pass_log:
          logging.error(traceback.format_exc())
        resolved.append(None)
        pass
    return resolved
  return Future(callback=resolve)


def Race(futures, except_pass=None, default=_no_value):
  '''Returns a Future which resolves to the first Future in |futures| that
  either succeeds or throws an error apart from those in |except_pass|.

  If all Futures throw errors in |except_pass| then |default| is returned,
  if specified. If |default| is not specified then one of the passed errors
  will be re-thrown, for a nice stack trace.
  '''
  def resolve():
    first_future = None
    for future in futures:
      if first_future is None:
        first_future = future
      try:
        return future.Get()
      # "except None" will simply not catch any errors.
      except except_pass:
        pass
    if default is not _no_value:
      return default
    # Everything failed and there is no default value, propagate the first
    # error even though it was caught by |except_pass|.
    return first_future.Get()
  return Future(callback=resolve)


class Future(object):
  '''Stores a value, error, or callback to be used later.
  '''
  def __init__(self, value=_no_value, callback=None, exc_info=None):
    self._value = value
    self._callback = callback
    self._exc_info = exc_info
    if (self._value is _no_value and
        self._callback is None and
        self._exc_info is None):
      raise ValueError('Must have either a value, error, or callback.')

  def Then(self, callback, error_handler=_DefaultErrorHandler):
    '''Creates and returns a future that runs |callback| on the value of this
    future, or runs optional |error_handler| if resolving this future results in
    an exception.

    If |callback| returns a non-Future value then the returned Future will
    resolve to that value.

    If |callback| returns a Future then it gets chained to the current Future.
    This means that the returned Future will resolve to *that* Future's value.
    This behaviour is transitive.

    For example,

      def fortytwo():
        return Future(value=42)

      def inc(x):
        return x + 1

      def inc_future(x):
        return Future(value=x + 1)

    fortytwo().Then(inc).Get()                         ==> 43
    fortytwo().Then(inc_future).Get()                  ==> 43
    fortytwo().Then(inc_future).Then(inc_future).Get() ==> 44
    '''
    def then():
      val = None
      try:
        val = self.Get()
      except Exception as e:
        val = error_handler(e)
      else:
        val = callback(val)
      return val.Get() if isinstance(val, Future) else val
    return Future(callback=then)

  def Get(self):
    '''Gets the stored value, error, or callback contents.
    '''
    if self._value is not _no_value:
      return self._value
    if self._exc_info is not None:
      self._Raise()
    try:
      self._value = self._callback()
      return self._value
    except:
      self._exc_info = sys.exc_info()
      self._Raise()

  def _Raise(self):
    exc_info = self._exc_info
    raise exc_info[0], exc_info[1], exc_info[2]