你好!我叫Dima,我是Wrike的前端开发人员。我们用Dart编写了项目的客户端部分,但是我们必须使用异步操作,这至少需要使用其他技术。区域是Dart为此提供的便捷工具之一。但是在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).
!