Docserver: Update Future.Then() to be more Promise-like

BUG=306341
NOTRY=True

Review URL: https://codereview.chromium.org/417163004

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@287482 0039d316-1c4b-4281-b951-d872f2087c98
parent f0938d1e
......@@ -63,14 +63,12 @@ class CachingFileSystem(FileSystem):
if dir_stat is not None:
return Future(value=make_stat_info(dir_stat))
dir_stat_future = self._MemoizedStatAsyncFromFileSystem(dir_path)
def resolve():
dir_stat = dir_stat_future.Get()
def next(dir_stat):
assert dir_stat is not None # should have raised a FileNotFoundError
# We only ever need to cache the dir stat.
self._stat_object_store.Set(dir_path, dir_stat)
return make_stat_info(dir_stat)
return Future(callback=resolve)
return self._MemoizedStatAsyncFromFileSystem(dir_path).Then(next)
@memoize
def _MemoizedStatAsyncFromFileSystem(self, dir_path):
......@@ -93,18 +91,17 @@ class CachingFileSystem(FileSystem):
# with a value.
stat_futures = {}
def swallow_file_not_found_error(future):
def resolve():
try: return future.Get()
except FileNotFoundError: return Nnone
return Future(callback=resolve)
def handle(error):
if isinstance(error, FileNotFoundError):
return None
raise error
for path in paths:
stat_value = cached_stat_values.get(path)
if stat_value is None:
stat_future = self.StatAsync(path)
if skip_not_found:
stat_future = swallow_file_not_found_error(stat_future)
stat_future = stat_future.Then(lambda x: x, handle)
else:
stat_future = Future(value=stat_value)
stat_futures[path] = stat_future
......@@ -120,19 +117,16 @@ class CachingFileSystem(FileSystem):
# Everything was cached and up-to-date.
return Future(value=fresh_data)
# Read in the values that were uncached or old.
read_futures = self._file_system.Read(
set(paths) - set(fresh_data.iterkeys()),
skip_not_found=skip_not_found)
def resolve():
new_results = read_futures.Get()
def next(new_results):
# Update the cache. This is a path -> (data, version) mapping.
self._read_object_store.SetMulti(
dict((path, (new_result, stat_futures[path].Get().version))
for path, new_result in new_results.iteritems()))
new_results.update(fresh_data)
return new_results
return Future(callback=resolve)
# Read in the values that were uncached or old.
return self._file_system.Read(set(paths) - set(fresh_data.iterkeys()),
skip_not_found=skip_not_found).Then(next)
def GetIdentity(self):
return self._file_system.GetIdentity()
......
......@@ -32,13 +32,11 @@ class ChrootFileSystem(FileSystem):
prefixed = posixpath.join(self._root, path)
prefixed_paths[prefixed] = path
return prefixed
future_result = self._file_system.Read(
tuple(prefix(path) for path in paths),
skip_not_found=skip_not_found)
def resolve():
def next(results):
return dict((prefixed_paths[path], content)
for path, content in future_result.Get().iteritems())
return Future(callback=resolve)
for path, content in results.iteritems())
return self._file_system.Read(tuple(prefix(path) for path in paths),
skip_not_found-skip_not_found).Then(next)
def Refresh(self):
return self._file_system.Refresh()
......
......@@ -129,11 +129,10 @@ class MockURLFetcher(object):
def FetchAsync(self, url, **kwargs):
self._fetch_async_count += 1
future = self._fetcher.FetchAsync(url, **kwargs)
def resolve():
def next(result):
self._fetch_resolve_count += 1
return future.Get()
return Future(callback=resolve)
return result
return self._fetcher.FetchAsync(url, **kwargs).Then(next)
def CheckAndReset(self,
fetch_count=0,
......
......@@ -106,13 +106,12 @@ class FileSystem(object):
return Future(value=True)
parent, base = SplitParent(path)
list_future = self.ReadSingle(ToDirectory(parent))
def resolve():
try:
return base in list_future.Get()
except FileNotFoundError:
def handle(error):
if isinstance(error, FileNotFoundError):
return False
return Future(callback=resolve)
raise error
return self.ReadSingle(ToDirectory(parent)).Then(lambda l: base in l,
handle)
def Refresh(self):
'''Asynchronously refreshes the content of the FileSystem, returning a
......
......@@ -7,6 +7,10 @@ import sys
_no_value = object()
def _DefaultErrorHandler(error):
raise error
def All(futures, except_pass=None):
'''Creates a Future which returns a list of results from each Future in
|futures|.
......@@ -61,12 +65,17 @@ class Future(object):
self._exc_info is None):
raise ValueError('Must have either a value, error, or callback.')
def Then(self, callback):
def Then(self, callback, error_handler=_DefaultErrorHandler):
'''Creates and returns a future that runs |callback| on the value of this
future.
future, or runs optional |error_handler| if resolving this future results in
an exception.
'''
def then():
return callback(self.Get())
try:
val = self.Get()
except Exception as e:
return error_handler(e)
return callback(val)
return Future(callback=then)
def Get(self):
......
......@@ -159,6 +159,63 @@ class FutureTest(unittest.TestCase):
except_pass=(ValueError,))
self.assertRaises(ValueError, race.Get)
def testThen(self):
def assertIs42(val):
self.assertEquals(val, 42)
then = Future(value=42).Then(assertIs42)
# Shouldn't raise an error.
then.Get()
# Test raising an error.
then = Future(value=41).Then(assertIs42)
self.assertRaises(AssertionError, then.Get)
# Test setting up an error handler.
def handle(error):
if isinstance(error, ValueError):
return 'Caught'
raise error
def raiseValueError():
raise ValueError
def raiseException():
raise Exception
then = Future(callback=raiseValueError).Then(assertIs42, handle)
self.assertEquals(then.Get(), 'Caught')
then = Future(callback=raiseException).Then(assertIs42, handle)
self.assertRaises(Exception, then.Get)
# Test chains of thens.
addOne = lambda val: val + 1
then = Future(value=40).Then(addOne).Then(addOne).Then(assertIs42)
# Shouldn't raise an error.
then.Get()
# Test error in chain.
then = Future(value=40).Then(addOne).Then(assertIs42).Then(addOne)
self.assertRaises(AssertionError, then.Get)
# Test handle error in chain.
def raiseValueErrorWithVal(val):
raise ValueError
then = Future(value=40).Then(addOne).Then(raiseValueErrorWithVal).Then(
addOne, handle).Then(lambda val: val + ' me')
self.assertEquals(then.Get(), 'Caught me')
# Test multiple handlers.
def myHandle(error):
if isinstance(error, AssertionError):
return 10
raise error
then = Future(value=40).Then(assertIs42).Then(addOne, handle).Then(addOne,
myHandle)
self.assertEquals(then.Get(), 10)
if __name__ == '__main__':
unittest.main()
......@@ -42,16 +42,15 @@ class MockFileSystem(FileSystem):
from |_updates|, if any.
'''
self._read_count += 1
future_result = self._file_system.Read(paths, skip_not_found=skip_not_found)
def resolve():
def next(result):
self._read_resolve_count += 1
result = future_result.Get()
for path in result.iterkeys():
update = self._GetMostRecentUpdate(path)
if update is not None:
result[path] = update
return result
return Future(callback=resolve)
return self._file_system.Read(paths,
skip_not_found=skip_not_found).Then(next)
def Refresh(self):
return self._file_system.Refresh()
......
......@@ -60,7 +60,7 @@ class MockFileSystemTest(unittest.TestCase):
future = fs.Read(['notfound.html', 'apps/'])
self.assertTrue(*fs.CheckAndReset(read_count=1))
self.assertRaises(FileNotFoundError, future.Get)
self.assertTrue(*fs.CheckAndReset(read_resolve_count=1))
self.assertTrue(*fs.CheckAndReset(read_resolve_count=0))
fs.Stat('404.html')
fs.Stat('404.html')
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment