Dart中的区域:心脏直视手术

你好!我叫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).


, . , , . AngularDart — , .



!




All Articles