Index: sdk/lib/async/zone.dart |
diff --git a/sdk/lib/async/zone.dart b/sdk/lib/async/zone.dart |
index 9f015255d5b5547da939c5516f1656d5b3e8a63a..f0394d66683135687b457c4a57d77a7f55fbd06b 100644 |
--- a/sdk/lib/async/zone.dart |
+++ b/sdk/lib/async/zone.dart |
@@ -7,6 +7,7 @@ part of dart.async; |
typedef R ZoneCallback<R>(); |
typedef R ZoneUnaryCallback<R, T>(T arg); |
typedef R ZoneBinaryCallback<R, T1, T2>(T1 arg1, T2 arg2); |
+ |
typedef T TaskCreate<T, S extends TaskSpecification>( |
S specification, Zone zone); |
typedef void TaskRun<T, A>(T task, A arg); |
@@ -259,14 +260,26 @@ class _ZoneSpecification implements ZoneSpecification { |
} |
/** |
- * This class wraps zones for delegation. |
+ * An adapted view of the parent zone. |
+ * |
+ * This class allows the implementation of a zone method to invoke methods on |
+ * the parent zone while retaining knowledge of the originating zone. |
+ * |
+ * Custom zones (created through `Zone.fork` or `runZoned`) can provide |
+ * implementations of most methods of zones. This is similar to overriding |
+ * methods on [Zone], except that this mechanism doesn't require subclassing. |
* |
- * When forwarding to parent zones one can't just invoke the parent zone's |
- * exposed functions (like [Zone.run]), but one needs to provide more |
- * information (like the zone the `run` was initiated). Zone callbacks thus |
- * receive more information including this [ZoneDelegate] class. When delegating |
- * to the parent zone one should go through the given instance instead of |
- * directly invoking the parent zone. |
+ * A custom zone function (provided through a [ZoneSpecification]) typically |
+ * records or wraps its parameters and then delegates the operation to its |
+ * parent zone using the provided [ZoneDelegate]. |
+ * |
+ * While zones have access to their parent zone (through [Zone.parent]) it is |
+ * recommended to call the methods on the provided parent delegate for two |
+ * reasons: |
+ * 1. the delegate methods take an additional `zone` argument which is the |
+ * zone the action has been initiated in. |
+ * 2. delegate calls are more efficient way, since the implementation knows how |
Lasse Reichstein Nielsen
2016/06/30 14:37:47
delete "way".
floitsch
2016/07/01 04:03:40
Done.
|
+ * to skip zones that would just delegate to their parents. |
*/ |
abstract class ZoneDelegate { |
/*=R*/ handleUncaughtError/*<R>*/( |
@@ -296,39 +309,133 @@ abstract class ZoneDelegate { |
} |
/** |
- * A Zone represents the asynchronous version of a dynamic extent. Asynchronous |
- * callbacks are executed in the zone they have been queued in. For example, |
- * the callback of a `future.then` is executed in the same zone as the one where |
- * the `then` was invoked. |
+ * A zone represents an environment that remains stable across asynchronous |
+ * calls, and which is responsible for handling uncaught asynchronous errors, |
+ * or operations such as [print] and [scheduleMicrotask]. |
+ * |
+ * Asynchronous callbacks are executed in the zone they have been queued in. For |
+ * example, the callback of a `future.then` is executed in the same zone as the |
+ * one where the `then` was invoked. |
+ * |
+ * Code is always executed inside a zone. When a program is started, the |
+ * default zone ([Zone.ROOT]) is installed. Users can provide |
+ * shadowing, nested zones. |
+ * |
+ * The [Zone] class is not subclassable, but users can provide custom zones by |
+ * forking an existing zone (usually [Zone.current]) with a [ZoneSpecification]. |
+ * Zone specifications contain intercepting functions that are invoked when the |
+ * zone members are invoked. As such, they provide the same functionality as |
+ * subclassing (but allow for a more efficient implementation). |
+ * |
+ * Asynchronous callbacks always return in the zone in which they have been |
+ * scheduled. This happens in two steps: |
+ * - the callback is registered using one of [registerCallback], |
+ * [registerUnaryCallback], or [registerBinaryCallback]. |
+ * - the asynchronous operation (such as [Future.then] or [Stream.listen]) |
+ * remember the current zone. Either, they store the zone in a data structure |
+ * (as is done for [Future]s), or they wrap the callback to capture the |
+ * current zone. A convenience function [bindCallback] (and the corresponding |
+ * [bindUnaryCallback] or [bindBindaryCallback]) perform the registration and |
+ * wrapping at the same time. |
+ * |
+ * Note that all asynchronous primitives (like [Timer.run]) have to be |
+ * implemented by the embedder, and that users generally don't need to worry |
+ * about keeping track of zones. However, new embedders (or native extensions) |
+ * need to ensure that new asynchronous primitives (like for example |
+ * `requestAnimationFrame` in the HTML library) respect this contract. |
*/ |
abstract class Zone { |
// Private constructor so that it is not possible instantiate a Zone class. |
Zone._(); |
- /** The root zone that is implicitly created. */ |
+ /** |
+ * The root zone that is implicitly created. |
+ * |
+ * The root zone implements the default behavior of all zone operations. |
+ * Many methods, like [registerCallback] don't do anything, others, like |
+ * [scheduleMicrotask] interact with the embedder to implement the desired |
+ * behavior. |
+ */ |
static const Zone ROOT = _ROOT_ZONE; |
/** The currently running zone. */ |
static Zone _current = _ROOT_ZONE; |
+ /** The zone that is currently active. */ |
static Zone get current => _current; |
+ /** |
+ * Handles uncaught asynchronous errors. |
+ * |
+ * Most asynchronous classes, like [Future] or [Stream] push errors to their |
+ * listeners. Errors are propagated this way, until, either a listener handles |
+ * the error (for example with [Future.catchError]), or no listener is |
+ * available anymore. In the latter case, futures and streams invoke the |
+ * zone's [handleUncaughtError]. |
+ * |
+ * By default, in the root zone, uncaught asynchronous errors are treated |
+ * like synchronous uncaught exceptions (although the root zone defers the |
+ * reporting by a microtask, to give other microtasks the opportunity to run |
+ * one last time). |
+ */ |
/*=R*/ handleUncaughtError/*<R>*/(error, StackTrace stackTrace); |
/** |
* Returns the parent zone. |
* |
* Returns `null` if `this` is the [ROOT] zone. |
+ * |
+ * Zones are created by [fork] (or [runZoned] which forks the [current] zone) |
+ * on an existing zone. The new zone keeps the forking zone as [parent] zone. |
*/ |
Zone get parent; |
/** |
* The error zone is the one that is responsible for dealing with uncaught |
* errors. |
- * Errors are not allowed to cross between zones with different error-zones. |
* |
- * This is the closest parent or ancestor zone of this zone that has a custom |
+ * This is the closest parent zone of this zone that provides a |
* [handleUncaughtError] method. |
+ * |
+ * Asynchronous errors never cross zone boundaries of zones with different |
+ * error-zones. |
+ * |
+ * Example: |
+ * ``` |
+ * import 'dart:async'; |
+ * |
+ * main() { |
+ * var future; |
+ * runZoned(() { |
+ * // The asynchronous error is caught by the custom zone which prints |
+ * // 'asynchronous error'. |
+ * future = new Future.error("asynchronous error"); |
+ * }, onError: (e) { print(e); }); // Creates a zone with an error handler. |
+ * // The following `catchError` is never reached, because the custom zone |
+ * // that is created by the call to `runZoned` provides an error handler. |
+ * future.catchError((e) { throw "is never reached"; }); |
+ * } |
+ * ``` |
+ * |
+ * Note that errors are not entering zones with different error handlers |
+ * either: |
+ * ``` |
+ * import 'dart:async'; |
+ * |
+ * main() { |
+ * runZoned(() { |
+ * // The following asynchronous error is *not* caught by the `catchError` |
+ * // in the nested zone, since errors are not to cross zone boundaries |
+ * // with different error handlers. |
+ * // Instead the error is handled by the current error handler, |
+ * // printing "Caught by outer zone: asynchronous error". |
+ * var future = new Future.error("asynchronous error"); |
+ * runZoned(() { |
+ * future.catchError((e) { throw "is never reached"; }); |
+ * }, onError: (e) { throw "is never reached"; }); |
+ * }, onError: (e) { print("Caught by outer zone: $e"); }); |
+ * } |
+ * ``` |
*/ |
Zone get errorZone; |
@@ -336,62 +443,87 @@ abstract class Zone { |
* Returns true if `this` and [otherZone] are in the same error zone. |
* |
* Two zones are in the same error zone if they inherit their |
- * [handleUncaughtError] callback from the same [errorZone]. |
+ * [errorZone] is the same. |
*/ |
bool inSameErrorZone(Zone otherZone); |
/** |
* Creates a new zone as a child of `this`. |
* |
- * The new zone will have behavior like the current zone, except where |
- * overridden by functions in [specification]. |
+ * The new zone uses the closures in the given [specification] to override |
+ * the current's zone behavior. All specification entries that are `null` |
+ * are automatically delegated to the parent zone (`this`). |
* |
- * The new zone will have the same stored values (accessed through |
+ * The new zone has the same stored values (accessed through |
* `operator []`) as this zone, but updated with the keys and values |
* in [zoneValues]. If a key is in both this zone's values and in |
- * `zoneValues`, the new zone will use the value from `zoneValues``. |
+ * `zoneValues`, the new zone uses the value from `zoneValues`. |
+ * |
+ * Note that the fork operation is interceptible. A zone can thus replace |
+ * the zone specification (or zone value), giving the parent zone full control |
+ * over the child zone. |
*/ |
- Zone fork({ ZoneSpecification specification, |
- Map zoneValues }); |
+ Zone fork({ZoneSpecification specification, |
+ Map zoneValues}); |
/** |
* Executes the given function [f] in this zone. |
+ * |
+ * By default (as implemented in the [ROOT] zone, this updates the [current] |
+ * zone to this zone, and executes [f]. |
+ * |
+ * Since the root zone is the only zone that can modify the [current] getter, |
+ * custom zones have to delegate to their parent zone if they wish to run |
+ * in their zone (which is generally the recommended behavior). |
*/ |
/*=R*/ run/*<R>*/(/*=R*/ f()); |
/** |
* Executes the given callback [f] with argument [arg] in this zone. |
+ * |
+ * See [run]. |
*/ |
/*=R*/ runUnary/*<R, T>*/(/*=R*/ f(/*=T*/ arg), /*=T*/ arg); |
/** |
* Executes the given callback [f] with argument [arg1] and [arg2] in this |
* zone. |
+ * |
+ * See [run]. |
*/ |
/*=R*/ runBinary/*<R, T1, T2>*/( |
/*=R*/ f(/*=T1*/ arg1, /*=T2*/ arg2), /*=T1*/ arg1, /*=T2*/ arg2); |
/** |
- * Executes the given function [f] in this zone. |
+ * Executes the given function [f] in this zone and catches synchronous |
+ * errors. |
+ * |
+ * This function is equivalent to: |
+ * ``` |
+ * try { |
+ * return run(f); |
+ * } catch (e, s) { |
+ * return handleUncaughtError(e, s); |
+ * } |
+ * ``` |
* |
- * Same as [run] but catches uncaught errors and gives them to |
- * [handleUncaughtError]. |
+ * See [run]. |
*/ |
/*=R*/ runGuarded/*<R>*/(/*=R*/ f()); |
/** |
- * Executes the given callback [f] in this zone. |
+ * Executes the given callback [f] with argument [arg] in this zone and |
+ * catches synchronous errors. |
* |
- * Same as [runUnary] but catches uncaught errors and gives them to |
- * [handleUncaughtError]. |
+ * See [runGuarded]. |
*/ |
/*=R*/ runUnaryGuarded/*<R, T>*/(/*=R*/ f(/*=T*/ arg), /*=T*/ arg); |
/** |
- * Executes the given callback [f] in this zone. |
+ * Executes the given callback [f] with arguments [arg1] and [arg2] in this |
+ * zone. |
* |
- * Same as [runBinary] but catches uncaught errors and gives them to |
- * [handleUncaughtError]. |
+ * See [runGuarded]. |
*/ |
/*=R*/ runBinaryGuarded/*<R, T1, T2>*/( |
/*=R*/ f(/*=T1*/ arg1, /*=T2*/ arg2), /*=T1*/ arg1, /*=T2*/ arg2); |
@@ -407,6 +539,8 @@ abstract class Zone { |
* |
* Returns a potentially new callback that should be used in place of the |
* given [callback]. |
+ * |
+ * Custom zones may intercept this operation. |
*/ |
ZoneCallback/*<R>*/ registerCallback/*<R>*/(/*=R*/ callback()); |
@@ -460,45 +594,80 @@ abstract class Zone { |
/*=R*/ f(/*=T1*/ arg1, /*=T2*/ arg2), { bool runGuarded: true }); |
/** |
- * Intercepts errors when added programmatically to a `Future` or `Stream`. |
+ * Intercepts errors when added programatically to a `Future` or `Stream`. |
* |
- * When caling [Completer.completeError], [Stream.addError], |
+ * When calling [Completer.completeError], [Stream.addError], |
* or [Future] constructors that take an error or a callback that may throw, |
* the current zone is allowed to intercept and replace the error. |
* |
- * When other libraries use intermediate controllers or completers, such |
- * calls may contain errors that have already been processed. |
+ * There is no guarantee that an error is only sent through [errorCallback] |
+ * once. Libraries that use intermediate controllers or completers might |
+ * end up invoking [errorCallback] multiple times. |
+ * |
+ * Returns `null` if no replacement is desired. Otherwise returns an instance |
+ * of [AsyncError] holding the new pair of error and stack trace. |
* |
- * Return `null` if no replacement is desired. |
- * The original error is used unchanged in that case. |
- * Otherwise return an instance of [AsyncError] holding |
- * the new pair of error and stack trace. |
- * If the [AsyncError.error] is `null`, it is replaced by a [NullThrownError]. |
+ * Although not recommended, the returned instance may have its `error` member |
+ * ([AsyncError.error]) be equal to `null` in which case the error should be |
+ * replaced by a [NullThrownError]. |
+ * |
+ * Custom zones may intercept this operation. |
*/ |
AsyncError errorCallback(Object error, StackTrace stackTrace); |
/** |
* Runs [f] asynchronously in this zone. |
+ * |
+ * The global `scheduleMicrotask` delegates to the current zone's |
+ * [scheduleMicrotask]. The root zone's implementation interacts with the |
+ * embedder to schedule the given callback as microtask. |
+ * |
+ * Custom zones may intercept this operation (for example to wrap the given |
+ * callback [f]). |
*/ |
void scheduleMicrotask(void f()); |
/** |
* Creates a task, given a [create] function and a [specification]. |
* |
- * The [create] function is invoked with the [specification] as argument. It |
- * returns a task object which is used for all future interactions with the |
- * zone. |
- * |
- * Custom zones may replace the [specification] with a different one, thus |
- * modifying the task parameters. |
+ * By default, in the root zone, the [create] function is invoked with the |
+ * [specification] as argument. It returns a task object which is used for all |
+ * future interactions with the zone. Generally, the object is a unique |
+ * instance that is also returned to whoever initiated the action. |
+ * For example, the HTML library uses the returned [StreamSubscription] as |
+ * task object, when users register an event listener. |
* |
- * Tasks are created when the program is starting an operation that returns |
- * through the event loop. For example, a timer or an http request both |
+ * Tasks are created when the program starts an operation that returns |
+ * through the event loop. For example, a timer or an HTTP request both |
* return through the event loop and are therefore tasks. |
* |
* If the [create] function is not invoked (because a custom zone has |
* replaced or intercepted it), then the operation is *not* started. This |
- * means that a custom zone can intercept tasks, like http requests. |
+ * means that a custom zone can intercept tasks, like HTTP requests. |
+ * |
+ * A task goes through the following steps: |
+ * - a user invokes a library function that should eventually return through |
+ * the event loop (and not just as a microtask). |
+ * - the library function creates a [TaskSpecification] that contains the |
+ * necessary information to start the operation, and invokes |
+ * `Zone.current.createTask` with the specification and a [create] closure. |
+ * The closure, when invoked, uses the specification to start the operation |
+ * (usually by interacting with the embedder, ar as a native extension), |
+ * and returns a task object that identifies the running task. |
+ * - custom zones handle the request and (unless completely intercepted and |
+ * aborted), end up calling the root zone's [createTask] which runs the |
+ * provided `create` closure (which may have been replaced at this point). |
+ * - later, the asynchronous operation returns through the event loop. |
+ * It invokes [Zone.runTask] on the zone the task should run on (which had |
+ * been given to the create function). The [runTask] function receives the |
+ * task object, a `run` function and an argument. As before, custom zones |
+ * may intercept this call. Eventually (unless aborted), the `run` function |
+ * is invoked, running Dart code that has been registered to run when the |
+ * task returns. This last step may happen multiple times for tasks that are |
+ * not oneshot tasks (see [ZoneSpecification.isOneShot]. |
+ * |
+ * Custom zones may replace the [specification] with a different one, thus |
+ * modifying the task parameters. |
*/ |
Object/*=T*/ createTask/*<T, S>*/( |
TaskCreate/*<T, S>*/ create, TaskSpecification/*=S*/ specification); |
@@ -517,6 +686,8 @@ abstract class Zone { |
* It is good practice, if task operations provide a meaningful [arg], so |
* that custom zones can deal with it. They might want to log it, or |
* replace it. |
+ * |
+ * See [createTask]. |
*/ |
void runTask/*<T, A>*/( |
TaskRun/*<T, A>*/ run, Object/*=T*/ task, Object/*=A*/ arg); |
@@ -535,6 +706,24 @@ abstract class Zone { |
/** |
* Prints the given [line]. |
+ * |
+ * The global `print` function delegates to the current zone's [print] |
+ * function which makes it possible to intercept the print function. |
+ * |
+ * Example: |
+ * ``` |
+ * import 'dart:async'; |
+ * |
+ * main() { |
+ * runZoned(() { |
+ * // Ends up printing: "Intercepted: in zone". |
+ * print("in zone"); |
+ * }, zoneSpecification: new ZoneSpecification( |
+ * print: (Zone self, ZoneDelegate parent, Zone zone, String line) { |
+ * parent.print(zone, "Intercepted: $line"); |
+ * })); |
+ * } |
+ * ``` |
*/ |
void print(String line); |