blob: ec938b8b85a609f0f36061f8ca2ef8562df7a541 [file] [log] [blame]
// 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 cleanUp(T successResult)}) {
Completer<List<Object>> completer;
int count = 0;
int errors = 0;
int 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 (int 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(0));
results = List(count);
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 cleanUp(T successResult)}) {
Completer<List<Object>> completer;
int count = 0;
bool hasError = false;
int 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) {
int 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 (int i = 0; i < results.length; i++) {
var result = results[i];
if (result != null) Future.sync(() => cleanUp(result));
}
}
results = List<Object>(count);
hasError = true;
}
results[i] = e;
checkDone();
});
}
if (count == 0) return Future.value(List(0));
results = List<T>(count);
completer = Completer();
return completer.future;
}
String toString() {
StringBuffer buffer = StringBuffer();
buffer.write("Multiple Errors:\n");
int linesPerError = _maxLines ~/ errors.length;
if (linesPerError < _minLinesPerError) {
linesPerError = _minLinesPerError;
}
for (int index = 0; index < errors.length; index++) {
var error = errors[index];
if (error == null) continue;
String errorString = error.toString();
int end = 0;
for (int 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();
}
}