salut! Je m'appelle Dima et je suis développeur frontend chez Wrike. Nous écrivons la partie client du projet dans Dart, mais nous devons travailler avec des opérations asynchrones pas moins qu'avec d'autres technologies. Les zones sont l'un des outils pratiques que Dart fournit pour cela. Mais dans la communauté Dart, vous trouvez rarement des informations utiles à ce sujet, j'ai donc décidé de comprendre et de vous en dire plus sur cet outil puissant.
Avis de non-responsabilité: tout le code utilisé dans cet article prétend uniquement être un copier-coller. En fait, je l'ai beaucoup simplifié et je me suis débarrassé des détails auxquels il ne fallait pas prêter attention dans le cadre de cet article. Pour préparer le matériel, nous avons utilisé la version 2.7.2 de Dart et la version 5.0.0 d'AngularDart.
Dart . . , dart:async, .
Wrike ( AngularDart) :
// Part of AngularDart component class
final NgZone _zone;
final ChangeDetectorRef _detector;
final Element _element;
void onSomeLifecycleHook() {
_zone.runOutsideAngular(() {
_element.onMouseMove.where(filterEvent).listen((event) {
doWork(event);
_zone.run(_detector.markForCheck);
});
});
}
Dart- , . , .
, , , :
- API , .
- .
- ( , ).
Dart , issues github. API, , , , , DartUP. , .
, :
- package:intl;
- package:quiver;
- package:angular.
.
Intl
intl — . : , , message plural .
:
class AppIntl {
static String loginLabel() => Intl.message(
'Login',
name: 'AppIntl_loginLabel',
);
}
. , , . , - . withLocale, :
// User has 'en' as default locale, but he works from Russia
final fallbackLocale = 'ru';
Future<Duration> parseText(String userText) async =>
// Try to parse user text
await _parseText(userText) ??
// Try to parse with 'ru' locale if default parsing failed
await Intl.withLocale(fallbackLocale, () => _parseText(userText));
// This is actual parser
Future<Duration> _parseText(String userText) {
// ...
}
-, fallback .
, withLocale , , . !
parseText Future, , . , - , . — , . — . .
, Future , , . .
1. Future
— ! - Future:
class Future {
Future() : _zone = Zone.current; // Save current zone on creation
final Zone _zone;
// ...
}
Future . . , then:
class Future {
// ...
Future<R> then<R>(
FutureOr<R> callback(T value), // ...
) {
// Notify zone about callback for async operation
callback = Zone.current.registerUnaryCallback(callback);
final result = Future();
// Schedule to complete [result] when async operation ends
_addListener(_FutureListener.then(
result,
callback, // ...
));
return result;
}
}
class _FutureListener {
// ...
FutureOr<T> handleValue(S value) =>
// Call scheduled work inside zone that was saved in [result] Future
result._zone.runUnary(_callback, value);
}
! , . Future, . Future , — Zone.current. runUnary . , , , . , - !
2. , , « »
It's an execution context. — Brian Ford, zone.js author.
— , «»: . , , . Future , , run*. -.
— , _current. Zone.current — _current. :
class Zone {
static Zone _current = _rootZone; // This is where current zone lives
static Zone get current => _current;
// ...
}
, . run* : run, runUnary, runBinary. _current:
class Zone {
// ...
R run<R>(R action()) {
Zone previous = _current;
// Place [this] zone in [_current] for a while
_current = this;
try {
return action(); // Then do stuff we wanted
} finally {
_current = previous; // Then revert current zone to previous
}
}
}
_current , . Zone.current .
! , , current , :
class _FutureListener {
// ...
FutureOr<T> handleValue(T value) => result._zone.runUnary(_callback, value);
}
class _FutureListener {
// ...
FutureOr<T> handleValue(T value) {
final previousZone = Zone.current;
Zone.current = result._zone;
final updatedValue = _callback(value);
Zone.current = previousZone;
return updatedValue;
}
}
. run* , , , . . .
, Intl , . - .
3. Intl
withLocale:
class Intl {
// ...
static withLocale(String locale, Function() callback) =>
// Create new zone with saved locale, then call callback inside it
runZoned(callback, zoneValues: {#Intl.locale: locale});
}
- ! .
runZoned . , runZoned . run* .
, — zoneValues. , . zoneValues ( Symbol).
, :
class Intl {
// ...
static String getCurrentLocale() {
// Get locale from current zone
var zoneLocale = Zone.current[#Intl.locale];
return zoneLocale == null ? _defaultLocale : zoneLocale;
}
// ...
}
! , . , . - , .
[], ( — ). []= — , , . - withLocale runZoned:
class Intl {
// ...
static withLocale(String locale, Function() callback) =>
// Create new zone with saved locale, then call callback inside it
runZoned(callback, zoneValues: {#Intl.locale: locale});
}
, .
, :
// User has 'en' as default locale, but he works from Russia
final fallbackLocale = 'ru';
Future<Duration> parseText(String userText) async =>
// Try to parse user text
await _parseText(userText) ??
// Try to parse with 'ru' locale if default parsing failed
await Intl.withLocale(fallbackLocale, () => _parseText(userText));
// This is actual parser
Future<Duration> _parseText(String userText) {
// ...
}
, withLocale , . , Future . _parseText _parseText. !
, Future . Future, Stream Timer «» . , . .
FakeAsync
- -. , . Dart . , test, . , Future test, expect, Future :
void main() {
test('do some testing', () {
return getAsyncResult().then((result) {
expect(result, isTrue);
});
});
}
— . , debounce , . «» mock , .
, . , . package:quiver FakeAsync.
:
import 'package:quiver/testing/async.dart';
void main() {
test('do some testing', () {
// Make FakeAsync object and run async code with it
FakeAsync().run((fakeAsync) {
getAsyncResult().then((result) {
expect(result, isTrue);
});
// Ask FakeAsync to flush all timers and microtasks
fakeAsync.flushTimers();
});
});
}
FakeAsync, , . .
, .
1. FakeAsync
run :
class FakeAsync {
// ...
dynamic run(callback(FakeAsync self)) {
// Make new zone if there wasn't any zone created before
_zone ??= Zone.current.fork(specification: _zoneSpec);
dynamic result;
// Call the test callback inside custom zone
_zone.runGuarded(() {
result = callback(this);
});
return result;
}
}
— , .
— fork specification.
Dart — root. , — Zone.root. root, root . run, ?
class Zone {
// ...
R run<R>(R action()) {
Zone previous = _current;
// Place [this] zone in [_current] for a while
_current = this;
try {
return action(); // Then do stuff we wanted
} finally {
_current = previous; // Then revert current zone to previous
}
}
}
« ». :
class _RootZone implements Zone {
// Only root zone can change current zone
// ...
R _run<R>(Zone self, ZoneDelegate parent, Zone zone, R action()) {
Zone previous = Zone._current;
// On this [zone] the .run() method was initially called
Zone._current = zone;
try {
return action(); // Then do stuff we wanted
} finally {
Zone._current = previous; // Then revert current zone to previous
}
}
}
!
— root . , - .
2. zoneSpecification
ZoneSpecification — zoneValues:
abstract class ZoneSpecification {
// All this handlers can be added during object creation
// ...
HandleUncaughtErrorHandler get handleUncaughtError;
RunHandler get run;
RunUnaryHandler get runUnary;
RunBinaryHandler get runBinary;
RegisterCallbackHandler get registerCallback;
RegisterUnaryCallbackHandler get registerUnaryCallback;
RegisterBinaryCallbackHandler get registerBinaryCallback;
ErrorCallbackHandler get errorCallback;
ScheduleMicrotaskHandler get scheduleMicrotask;
CreateTimerHandler get createTimer;
CreatePeriodicTimerHandler get createPeriodicTimer;
PrintHandler get print;
ForkHandler get fork;
}
, , . — -. , .
— , - :
// This is the actual type of run handler
typedef RunHandler = R Function<R>(
Zone self, // Reference to the zone with this specification
ZoneDelegate parent, // Object for delegating work to [self] parent zone
Zone zone, // On this zone .run() method was initially called
R Function() action, // The actual work we want to run in [zone]
);
int _counter = 0;
final zone = Zone.current.fork(
specification: ZoneSpecification(
// This will be called within [zone.run(doWork);]
run: <R>(self, parent, zone, action) {
// RunHandler
// Delegate an updated work to parent, so in addition
// to the work being done, the counter will also increase
parent.run(zone, () {
_counter += 1;
action();
});
},
),
);
void main() {
zone.run(doWork);
}
. run , run. - — , .
.
, . , -. .
. - , «» . , , , , root . , .
— , .
— , «» . , root , _current .
— , . , , , . , .
, . :
: , , D B
, .
3. - FakeAsync
FakeAsync. , run , . :
class FakeAsync {
// ...
ZoneSpecification get _zoneSpec => ZoneSpecification(
// ...
scheduleMicrotask: (_, __, ___, Function microtask) {
_microtasks.add(microtask); // Just save callback
},
createTimer: (_, __, ___, Duration duration, Function callback) {
// Save timer that can immediately provide its callback to us
var timer = _FakeTimer._(duration, callback, isPeriodic, this);
_timers.add(timer);
return timer;
},
);
}
scheduleMicrotask. , - , . , Future , Future . : Stream .
FakeAsync c — .
createTimer. createTimer, , , Timer. : « ?». :
abstract class Timer {
factory Timer(Duration duration, void callback()) {
// Create timer with current zone
return Zone.current
.createTimer(duration, Zone.current.bindCallbackGuarded(callback));
}
// ...
// Create timer from environment
external static Timer _createTimer(Duration duration, void callback());
}
class _RootZone implements Zone {
// ...
Timer createTimer(Duration duration, void f()) {
return Timer._createTimer(duration, f);
}
}
— , . , , . FakeAsync: _FakeTimer, — .
class FakeAsync {
// ...
ZoneSpecification get _zoneSpec => ZoneSpecification(
// ...
scheduleMicrotask: (_, __, ___, Function microtask) {
_microtasks.add(microtask); // Just save callback
},
createTimer: (_, __, ___, Duration duration, Function callback) {
// Save timer that can immediately provide its callback to us
var timer = _FakeTimer._(duration, callback, isPeriodic, this);
_timers.add(timer);
return timer;
},
);
}
run, . , . , . FakeAsync — , .
, ! flushTimers:
class FakeAsync {
// ...
void flushTimers() {
// Call timer callback for every saved timer
while (_timers.isNotEmpty) {
final timer = _timers.removeFirst();
timer._callback(timer);
// Call every microtask after processing each timer
_drainMicrotasks();
}
}
void _drainMicrotasks() {
while (_microtasks.isNotEmpty) {
final microtask = _microtasks.removeFirst();
microtask();
}
}
}
, , , . , !
, , ZoneSpecification. .
:
- (handleUncaughtError, errorCallback);
- (registerCallback*);
- (createPeriodicTimer);
- (print);
- (fork).
!