Zona di Dart: Buka Bedah Jantung untuk Sekitarnya

Halo! Nama saya Dima dan saya adalah pengembang frontend di Wrike. Kami menulis bagian klien dari proyek di Dart, tetapi kami harus bekerja dengan operasi asinkron tidak kurang dari dengan teknologi lainnya. Zona adalah salah satu alat praktis yang disediakan Dart untuk ini. Tetapi di komunitas Dart Anda jarang menemukan informasi berguna tentang hal itu, jadi saya memutuskan untuk memahami dan memberi tahu Anda lebih banyak tentang alat yang ampuh ini.





Penafian: Semua kode yang digunakan dalam artikel ini hanya berpura-pura menjadi copy-paste. Bahkan, saya banyak menyederhanakannya dan menyingkirkan perincian yang seharusnya tidak diperhatikan dalam konteks artikel ini. Dalam menyiapkan materi, kami menggunakan Dart versi 2.7.2 dan AngularDart versi 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