blob: 91abe5b568bda67f1a8b464516bc5489109e9049 [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 pkg.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 _MAX_LINES = 55;
// Minimum number of lines in the toString for each error.
static const int _MIN_LINES_PER_ERROR = 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> waitUnordered(Iterable<Future> futures,
{cleanUp(successResult)}) {
Completer completer;
int count = 0;
int errors = 0;
int values = 0;
// Initilized to `new List(count)` when count is known.
// Filled up with values on the left, errors on the right.
// Order is not preserved.
List results;
void checkDone() {
if (errors + values < count) return;
if (errors == 0) {
completer.complete(results);
return;
}
var errorList = results.sublist(results.length - errors);
completer.completeError(new MultiError(errorList));
};
var handleValue = (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) {
new 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) new Future.sync(() => cleanUp(value));
}
}
results[results.length - ++errors] = e;
checkDone();
};
for (Future future in futures) {
count++;
future.then(handleValue, onError: handleError);
}
if (count == 0) return new Future.value(new List(0));
results = new List(count);
completer = new 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> wait(Iterable<Future> futures,
{cleanUp(successResult)}) {
Completer completer;
int count = 0;
bool hasError = false;
int completed = 0;
// Initalized to `new List(count)` when count is known.
// Filled with values until the first error, then cleared
// and filled with errors.
List results;
void checkDone() {
completed++;
if (completed < count) return;
if (!hasError) {
completer.complete(results);
return;
}
completer.completeError(new MultiError(results));
};
for (Future future in futures) {
int i = count;
count++;
future.then((v) {
if (!hasError) {
results[i] = v;
} else if (cleanUp != null) {
new 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) new Future.sync(() => cleanUp(result));
}
}
results.fillRange(0, results.length, null);
hasError = true;
}
results[i] = e;
checkDone();
});
}
if (count == 0) return new Future.value(new List(0));
results = new List(count);
completer = new Completer();
return completer.future;
}
String toString() {
StringBuffer buffer = new StringBuffer();
buffer.write("Multiple Errors:\n");
int linesPerError = _MAX_LINES ~/ errors.length;
if (linesPerError < _MIN_LINES_PER_ERROR) {
linesPerError = _MIN_LINES_PER_ERROR;
}
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();
}
}