| // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| // TODO(lrn): This should be in package:async? |
| /// Helper functions for working with errors. |
| /// |
| /// The [MultiError] class combines multiple errors into one object, |
| /// and the [MultiError.wait] function works like [Future.wait] except |
| /// that it returns all the errors. |
| library isolate.errors; |
| |
| import 'dart:async'; |
| |
| class MultiError extends Error { |
| // Limits the number of lines included from each error's error message. |
| // A best-effort attempt is made at keeping below this number of lines |
| // in the output. |
| // If there are too many errors, they will all get at least one line. |
| static const int _maxLines = 55; |
| // Minimum number of lines in the toString for each error. |
| static const int _minLinesPerError = 1; |
| |
| /// The actual errors. |
| final List errors; |
| |
| /// Create a `MultiError` based on a list of errors. |
| /// |
| /// The errors represent errors of a number of individual operations. |
| /// |
| /// The list may contain `null` values, if the index of the error in the |
| /// list is useful. |
| MultiError(this.errors); |
| |
| /// Waits for all [futures] to complete, like [Future.wait]. |
| /// |
| /// Where `Future.wait` only reports one error, even if multiple |
| /// futures complete with errors, this function will complete |
| /// with a [MultiError] if more than one future completes with an error. |
| /// |
| /// The order of values is not preserved (if that is needed, use |
| /// [wait]). |
| static Future<List<Object>> waitUnordered<T>(Iterable<Future<T>> futures, |
| {void Function(T successResult) cleanUp}) { |
| Completer<List<Object>> completer; |
| var count = 0; |
| var errors = 0; |
| var values = 0; |
| // Initialized to `new List(count)` when count is known. |
| // Filled up with values on the left, errors on the right. |
| // Order is not preserved. |
| List<Object> results; |
| void checkDone() { |
| if (errors + values < count) return; |
| if (errors == 0) { |
| completer.complete(results); |
| return; |
| } |
| var errorList = results.sublist(results.length - errors); |
| completer.completeError(MultiError(errorList)); |
| } |
| |
| var handleValue = (T v) { |
| // If this fails because [results] is null, there is a future |
| // which breaks the Future API by completing immediately when |
| // calling Future.then, probably by misusing a synchronous completer. |
| results[values++] = v; |
| if (errors > 0 && cleanUp != null) { |
| Future.sync(() => cleanUp(v)); |
| } |
| checkDone(); |
| }; |
| var handleError = (e, s) { |
| if (errors == 0 && cleanUp != null) { |
| for (var i = 0; i < values; i++) { |
| var value = results[i]; |
| if (value != null) Future.sync(() => cleanUp(value)); |
| } |
| } |
| results[results.length - ++errors] = e; |
| checkDone(); |
| }; |
| for (var future in futures) { |
| count++; |
| future.then(handleValue, onError: handleError); |
| } |
| if (count == 0) return Future.value(List.filled(0, null)); |
| results = List.filled(count, null); |
| completer = Completer(); |
| return completer.future; |
| } |
| |
| /// Waits for all [futures] to complete, like [Future.wait]. |
| /// |
| /// Where `Future.wait` only reports one error, even if multiple |
| /// futures complete with errors, this function will complete |
| /// with a [MultiError] if more than one future completes with an error. |
| /// |
| /// The order of values is preserved, and if any error occurs, the |
| /// [MultiError.errors] list will have errors in the corresponding slots, |
| /// and `null` for non-errors. |
| Future<List<Object>> wait<T>(Iterable<Future<T>> futures, |
| {void Function(T successResult) cleanUp}) { |
| Completer<List<Object>> completer; |
| var count = 0; |
| var hasError = false; |
| var completed = 0; |
| // Initialized to `new List(count)` when count is known. |
| // Filled with values until the first error, then cleared |
| // and filled with errors. |
| List<Object> results; |
| void checkDone() { |
| completed++; |
| if (completed < count) return; |
| if (!hasError) { |
| completer.complete(results); |
| return; |
| } |
| completer.completeError(MultiError(results)); |
| } |
| |
| for (var future in futures) { |
| var i = count; |
| count++; |
| future.then((v) { |
| if (!hasError) { |
| results[i] = v; |
| } else if (cleanUp != null) { |
| Future.sync(() => cleanUp(v)); |
| } |
| checkDone(); |
| }, onError: (e, s) { |
| if (!hasError) { |
| if (cleanUp != null) { |
| for (var i = 0; i < results.length; i++) { |
| var result = results[i]; |
| if (result != null) Future.sync(() => cleanUp(result)); |
| } |
| } |
| results = List<Object>.filled(count, null); |
| hasError = true; |
| } |
| results[i] = e; |
| checkDone(); |
| }); |
| } |
| if (count == 0) return Future.value(List.filled(0, null)); |
| results = List<T>.filled(count, null); |
| completer = Completer(); |
| return completer.future; |
| } |
| |
| @override |
| String toString() { |
| var buffer = StringBuffer(); |
| buffer.write('Multiple Errors:\n'); |
| var linesPerError = _maxLines ~/ errors.length; |
| if (linesPerError < _minLinesPerError) { |
| linesPerError = _minLinesPerError; |
| } |
| |
| for (var index = 0; index < errors.length; index++) { |
| var error = errors[index]; |
| if (error == null) continue; |
| var errorString = error.toString(); |
| var end = 0; |
| for (var i = 0; i < linesPerError; i++) { |
| end = errorString.indexOf('\n', end) + 1; |
| if (end == 0) { |
| end = errorString.length; |
| break; |
| } |
| } |
| buffer.write('#$index: '); |
| buffer.write(errorString.substring(0, end)); |
| if (end < errorString.length) { |
| buffer.write('...\n'); |
| } |
| } |
| return buffer.toString(); |
| } |
| } |