こんにちは!私の名前はDimaで、Wrikeのフロントエンド開発者です。プロジェクトのクライアント部分はDartで記述しますが、非同期オペレーションは他のテクノロジーと同様に扱う必要があります。ゾーンは、Dartがこれを提供する便利なツールの1つです。しかし、Dartコミュニティでは、それに関する有用な情報を見つけることはめったにないため、この強力なツールについて理解し、詳しく説明することにしました。
免責事項:この記事で使用されているすべてのコードは、コピーと貼り付けのふりをしているだけです。実際、私はそれを大幅に簡略化し、この記事のコンテキストで注意を払うべきではない詳細を取り除きました。素材の準備には、Dartバージョン2.7.2とAngularDartバージョン5.0.0を使用しました。
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).
!