Zonas em dardo: cirurgia cardíaca aberta para arredores

Olá! Meu nome é Dima e sou desenvolvedor front-end da Wrike. Escrevemos a parte do cliente do projeto no Dart, mas temos que trabalhar com operações assíncronas não menos que com outras tecnologias. As zonas são uma das ferramentas úteis que o Dart fornece para isso. Mas na comunidade Dart você raramente encontra informações úteis sobre isso, então decidi entender e falar mais sobre essa ferramenta poderosa.





Isenção de responsabilidade: todo o código usado neste artigo está apenas fingindo ser copiar e colar. De fato, simplifiquei bastante e me livrei de detalhes que não deveriam ser prestados atenção no contexto deste artigo. Na preparação do material, usamos o Dart versão 2.7.2 e o AngularDart versão 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