Dart 庫預覽

http://dart.goodev.org/guides/libraries/library-tour#strings-and-regular-expressions

這里講介紹 Dart 核心庫中的主要特性和功能。 只是一個預覽,并不會面面俱到。 如果你想了解更詳細的信息,請參考 Dart API 文檔。

關于 Dart 語言的更多信息請參考: Dart 語言預覽。

dart:core - numbers, collections, strings, and more

Dart core 庫提供了少量的但是非常核心的功能。每個 Dart 應用都會自動導入這個 庫。

Numbers

dart:core 庫定義了 num, int, 和 double 類,這些類 定義一些操作數字的基礎功能。

使用 parse() 函數可以把字符串 轉換為 整數或者浮點數。

assert(int.parse('42') == 42);
assert(int.parse('0x42') == 66);
assert(double.parse('0.50') == 0.5);

num 類也定義了一個 parse() 函數,這個函數會嘗試解析 為整數,如果無法解析為整數,則會解析為浮點數(double)。

assert(num.parse('42') is int);
assert(num.parse('0x42') is int);
assert(num.parse('0.50') is double);

對于整數,可以指定一個 radix 作為基數:

assert(int.parse('42', radix: 16) == 66);

使用 toString() 函數 (由 Object 對象定義)來把整數或者浮點數 轉換為字符串。使用 num 類的函數 toStringAsFixed() 可以限定 小數點的位數。如果要指定轉換為字符串的有效位數, 則可以使用定義在 num 類的 函數 toStringAsPrecision():

// Convert an int to a string.
assert(42.toString() == '42');

// Convert a double to a string.
assert(123.456.toString() == '123.456');

// Specify the number of digits after the decimal.
assert(123.456.toStringAsFixed(2) == '123.46');

// Specify the number of significant figures.
assert(123.456.toStringAsPrecision(2) == '1.2e+2');
assert(double.parse('1.2e+2') == 120.0);

更多信息,參考 API 文檔中每個類的描述: int, double, 和 num。 另外還有 dart:math提供了關于數學運算的常用功能。

Strings and regular expressions(字符串和正則表達式)

Dart 中的字符串是一個不可變的 UTF-16 碼元(code units)序列。 在語言預覽中對 字符串有詳細的介紹。 你可以使用正則表達式(RegExp 對象) 來搜索字符串中的內容或者 替換部分字符串。

String 類定義了一些函數 split(), contains(), startsWith(), endsWith() 等來處理各種字符串操作。

Searching inside a string(在字符串內搜索) 可以在字符串內查找特定字符的位置,還可以 判斷字符串是否以特定字符串開始和結尾。 例如:

// Check whether a string contains another string.
assert('Never odd or even'.contains('odd'));

// Does a string start with another string?
assert('Never odd or even'.startsWith('Never'));

// Does a string end with another string?
assert('Never odd or even'.endsWith('even'));

// Find the location of a string inside a string.
assert('Never odd or even'.indexOf('odd') == 6);

Extracting data from a string(從字符串中提取數據)

可以從字符串中獲取到單個的字符,單個字符可以是 String 也可以是 int 值。 準確來說,實際得到的是一個 UTF-16 code units; 對于碼率比較大的字符,實際得到的是兩個 code units,例如 treble clef 符號 (‘\u{1D11E}’) 。

還可以從字符串中提取一個子字符串,或者把字符串分割為 多個子字符串:

// Grab a substring.
assert('Never odd or even'.substring(6, 9) == 'odd');

// Split a string using a string pattern.
var parts = 'structured web apps'.split(' ');
assert(parts.length == 3);
assert(parts[0] == 'structured');

// Get a UTF-16 code unit (as a string) by index.
assert('Never odd or even'[0] == 'N');

// Use split() with an empty string parameter to get
// a list of all characters (as Strings); good for
// iterating.
for (var char in 'hello'.split('')) {
  print(char);
}

// Get all the UTF-16 code units in the string.
var codeUnitList = 'Never odd or even'.codeUnits.toList();
assert(codeUnitList[0] == 78);

Converting to uppercase or lowercase(大小寫轉換)

字符串大小寫轉換是非常 簡單的:

// Convert to uppercase.
assert('structured web apps'.toUpperCase() ==
    'STRUCTURED WEB APPS');

// Convert to lowercase.
assert('STRUCTURED WEB APPS'.toLowerCase() ==
    'structured web apps');

注意: 注意上面的轉換方式對于某些語言是有問題的。例如對于土耳其語言 中的無點 I 的轉換就是錯誤的。

Trimming and empty strings(裁剪和判斷空字符串)

trim() 函數可以刪除字符串前后的空白字符。使用 isEmpty 可以 判斷字符串是否為空(長度為 0)。

// Trim a string.
assert('  hello  '.trim() == 'hello');

// Check whether a string is empty.
assert(''.isEmpty);

// Strings with only white space are not empty.
assert(!'  '.isEmpty);

Replacing part of a string(替換部分字符)

Strings 是不可變的對象,可以創建他們但是無法修改。 如果你仔細研究了 String API 文檔,你會注意到并沒有 函數可以修改字符串的狀態。 例如,函數 replaceAll() 返回一個新的 String 對象而不是修改 舊的對象:

var greetingTemplate = 'Hello, NAME!';
var greeting = greetingTemplate
    .replaceAll(new RegExp('NAME'), 'Bob');

assert(greeting !=
    greetingTemplate); // greetingTemplate didn't change.

Building a string(創建字符串)

使用 StringBuffer 可以在代碼中創建字符串。 只有當你調用 StringBuffer 的 toString() 函數的時候,才會創建一個 新的 String 對象。而 writeAll() 函數還有一個可選的參數來指定每個字符串的分隔符, 例如下面指定空格為分隔符:

var sb = new StringBuffer();
sb..write('Use a StringBuffer for ')
  ..writeAll(['efficient', 'string', 'creation'], ' ')
  ..write('.');

var fullString = sb.toString();

assert(fullString ==
    'Use a StringBuffer for efficient string creation.');

Regular expressions(正則表達式)

RegExp 類提供了 JavaScript 正則表達式同樣的功能。 正則表達式可以高效率的搜索和匹配 字符串。

// Here's a regular expression for one or more digits.
var numbers = new RegExp(r'\d+');

var allCharacters = 'llamas live fifteen to twenty years';
var someDigits = 'llamas live 15 to 20 years';

// contains() can use a regular expression.
assert(!allCharacters.contains(numbers));
assert(someDigits.contains(numbers));

// Replace every match with another string.
var exedOut = someDigits.replaceAll(numbers, 'XX');
assert(exedOut == 'llamas live XX to XX years');

還可以直接操作 RegExp 類。 Match 類提供了 訪問正則表達式匹配到的內容。

var numbers = new RegExp(r'\d+');
var someDigits = 'llamas live 15 to 20 years';

// Check whether the reg exp has a match in a string.
assert(numbers.hasMatch(someDigits));

// Loop through all matches.
for (var match in numbers.allMatches(someDigits)) {
  print(match.group(0)); // 15, then 20
}

More information(更多信息)

參考 String API 文檔 來查看 String 類的所有 方法。同時還可以參考下面這些類的 api 文檔: StringBuffer, Pattern, RegExp, 和 Match.

Collections

Dart 提供了一些核心的集合 API,包含 lists, sets, 和 maps。

Lists

在語言預覽中已經介紹過如何 創建 lists了。另外還可以 使用 List 構造函數來創建 List 對象。 List 類來定義了一些函數可以添加或者刪除里面的數據。

// Use a List constructor.
var vegetables = new List();

// Or simply use a list literal.
var fruits = ['apples', 'oranges'];

// Add to a list.
fruits.add('kiwis');

// Add multiple items to a list.
fruits.addAll(['grapes', 'bananas']);

// Get the list length.
assert(fruits.length == 5);

// Remove a single item.
var appleIndex = fruits.indexOf('apples');
fruits.removeAt(appleIndex);
assert(fruits.length == 4);

// Remove all elements from a list.
fruits.clear();
assert(fruits.length == 0);

使用 indexOf() 來查找 list 中對象的索引:

var fruits = ['apples', 'oranges'];

// Access a list item by index.
assert(fruits[0] == 'apples');

// Find an item in a list.
assert(fruits.indexOf('apples') == 0);

排序一個 list 可以使用 sort() 函數。還可以提供一個用來排序 的比較方法。排序方法返回值 為:對于小的值 為 < 0;對于相同的值為 0 ;對于大的值為 > 0。 下面的示例使用由 Comparable 定義的 compareTo() 函數,該函數也被 String 實現了。

var fruits = ['bananas', 'apples', 'oranges'];

// Sort a list.
fruits.sort((a, b) => a.compareTo(b));
assert(fruits[0] == 'apples');

List 是泛型類型,所以可以指定 里面所保存的數據類型:

// This list should contain only strings.
var fruits = new List<String>();

fruits.add('apples');
var fruit = fruits[0];
assert(fruit is String);

// Generates static analysis warning, num is not a string.
fruits.add(5);  // BAD: Throws exception in checked mode.

參考 List API 文檔 來查看 list 的 所有函數。

Sets

Dart 中的 Set 是一個無序集合,里面不能保護重復的數據。 由于是無序的,所以無法通過索引來從 set 中獲取數據:

var ingredients = new Set();
ingredients.addAll(['gold', 'titanium', 'xenon']);
assert(ingredients.length == 3);

// Adding a duplicate item has no effect.
ingredients.add('gold');
assert(ingredients.length == 3);

// Remove an item from a set.
ingredients.remove('gold');
assert(ingredients.length == 2);

使用 contains()containsAll() 來判斷 set 中是否包含 一個或者多個對象:

var ingredients = new Set();
ingredients.addAll(['gold', 'titanium', 'xenon']);

// Check whether an item is in the set.
assert(ingredients.contains('titanium'));

// Check whether all the items are in the set.
assert(ingredients.containsAll(['titanium', 'xenon']));
交際是兩個 set 中都有的數據的子集:

var ingredients = new Set();
ingredients.addAll(['gold', 'titanium', 'xenon']);

// Create the intersection of two sets.
var nobleGases = new Set.from(['xenon', 'argon']);
var intersection = ingredients.intersection(nobleGases);
assert(intersection.length == 1);
assert(intersection.contains('xenon'));

Maps

map 通常也被稱之為 字典或者 hash ,也是一個無序的集合,里面 包含一個 key-value 對。map 把 key 和 value 關聯起來可以 方便獲取數據。和 JavaScript 不同的是, Dart objects 不是 maps。

下面是兩種定義 map 對象的方式:

// Maps often use strings as keys.
var hawaiianBeaches = {
  'Oahu'      : ['Waikiki', 'Kailua', 'Waimanalo'],
  'Big Island': ['Wailea Bay', 'Pololu Beach'],
  'Kauai'     : ['Hanalei', 'Poipu']
};

// Maps can be built from a constructor.
var searchTerms = new Map();

// Maps are parameterized types; you can specify what
// types the key and value should be.
var nobleGases = new Map<int, String>();

使用中括號來訪問或者設置 map 中的數據, 使用 remove() 函數來從 map 中刪除 key 和 value。

var nobleGases = {54: 'xenon'};

// Retrieve a value with a key.
assert(nobleGases[54] == 'xenon');

// Check whether a map contains a key.
assert(nobleGases.containsKey(54));

// Remove a key and its value.
nobleGases.remove(54);
assert(!nobleGases.containsKey(54));

還可以獲取 map 的所有 key 和 value:

var hawaiianBeaches = {
  'Oahu'      : ['Waikiki', 'Kailua', 'Waimanalo'],
  'Big Island': ['Wailea Bay', 'Pololu Beach'],
  'Kauai'     : ['Hanalei', 'Poipu']
};

// Get all the keys as an unordered collection
// (an Iterable).
var keys = hawaiianBeaches.keys;

assert(keys.length == 3);
assert(new Set.from(keys).contains('Oahu'));

// Get all the values as an unordered collection
// (an Iterable of Lists).
var values = hawaiianBeaches.values;
assert(values.length == 3);
assert(values.any((v) => v.contains('Waikiki')));

containsKey() 判斷 map 是否包含一個 key。由于 map 的 value 可以為 null, 所有通過 key 來獲取 value 并通過 判斷 value 是否為 null 來判斷 key 是否存在是 行不通的。所以添加了一個判斷 key 是否存在的函數:

var hawaiianBeaches = {
  'Oahu'      : ['Waikiki', 'Kailua', 'Waimanalo'],
  'Big Island': ['Wailea Bay', 'Pololu Beach'],
  'Kauai'     : ['Hanalei', 'Poipu']
};

assert(hawaiianBeaches.containsKey('Oahu'));
assert(!hawaiianBeaches.containsKey('Florida'));

map 還有一個 putIfAbsent() 函數來設置 key 的值,但是只有該 key 在 map 中不存在的時候才設置這個值,否則 key 的值保持不變。該函數需要 一個方法返回 value:

var teamAssignments = {};
teamAssignments.putIfAbsent(
    'Catcher', () => pickToughestKid());
assert(teamAssignments['Catcher'] != null);

Common collection methods(常用的集合函數)

List, Set, 和 Map 上可以使用很多常用的集合函數。 Iterable 類定義了一些常用的功能, List 和 Set 實現了 Iterable 。

注意: 雖然 Map 沒有實現 Iterable,但是 Map 的 keys 和 values 屬性實現了 Iterable。

可以使用 isEmpty 函數來判斷集合是否為空的:

var teas = ['green', 'black', 'chamomile', 'earl grey'];
assert(!teas.isEmpty);

使用 forEach() 函數可以對集合中的每個數據都應用 一個方法:

var teas = ['green', 'black', 'chamomile', 'earl grey'];

teas.forEach((tea) => print('I drink $tea'));

在 Map 上使用 forEach() 的時候,方法需要能 接收兩個參數(key 和 value):

hawaiianBeaches.forEach((k, v) {
  print('I want to visit $k and swim at $v');
  // I want to visit Oahu and swim at
  // [Waikiki, Kailua, Waimanalo], etc.
});

Iterables 也有一個 map() 函數,這個函數返回一個包含所有數據的對象:

var teas = ['green', 'black', 'chamomile', 'earl grey'];

var loudTeas = teas.map((tea) => tea.toUpperCase());
loudTeas.forEach(print);

注意: map() 函數返回的對象也是一個 Iterable,該對象是懶求值(lazily evaluated) 的,只有當訪問里面的值的時候, map 的方法才被調用。

可以使用 map().toList() 或者 map().toSet() 來 強制立刻執行 map 的方法:

var loudTeaList = teas
    .map((tea) => tea.toUpperCase())
    .toList();

Iterable 的 where() 函數可以返回所有滿足特定條件的數據。 any() 判斷是否有數據滿足特定條件, every() 判斷是否所有數據都滿足 特定條件。

var teas = ['green', 'black', 'chamomile', 'earl grey'];

// Chamomile is not caffeinated.
bool isDecaffeinated(String teaName) =>
    teaName == 'chamomile';

// Use where() to find only the items that return true
// from the provided function.
var decaffeinatedTeas = teas
    .where((tea) => isDecaffeinated(tea));
// or teas.where(isDecaffeinated)

// Use any() to check whether at least one item in the
// collection satisfies a condition.
assert(teas.any(isDecaffeinated));

// Use every() to check whether all the items in a
// collection satisfy a condition.
assert(!teas.every(isDecaffeinated));

URIs

Uri 類 提供了 編碼和解碼 URI(URL) 字符的功能。 這些函數處理 URI 特殊的字符,例如 & 和 =。 Uri 類還可以解析和處理 URI 的每個部分,比如 host, port, scheme 等。

Encoding and decoding fully qualified URIs(編碼解碼URI) 要編碼和解碼除了 URI 中特殊意義(例如 /, :, &, #)的字符, 則可以使用 encodeFull() 和 decodeFull() 函數。這兩個函數可以用來編碼和解碼整個 URI,并且保留 URI 特殊意義的字符不變。

var uri = 'http://example.org/api?foo=some message';

var encoded = Uri.encodeFull(uri);
assert(encoded ==
    'http://example.org/api?foo=some%20message');

var decoded = Uri.decodeFull(encoded);
assert(uri == decoded);

注意上面 some 和 message 之間的空格被編碼了。

Encoding and decoding URI components(編碼解碼URI組件)

使用 encodeComponent() 和 decodeComponent() 可以編碼 和解碼 URI 中的所有字符,特殊意義的字符(/, &, 和 : 等) 也會編碼,

var uri = 'http://example.org/api?foo=some message';

var encoded = Uri.encodeComponent(uri);
assert(encoded ==
    'http%3A%2F%2Fexample.org%2Fapi%3Ffoo%3Dsome%20message');

var decoded = Uri.decodeComponent(encoded);
assert(uri == decoded);

注意上面特殊字符也被編碼了,比如 / 編碼為 %2F。

Parsing URIs

如果有個 Uri 對象或者 URI 字符串,使用 Uri 的屬性 可以獲取每個部分,比如 path。使用 parse() 靜態 函數可以從字符串中解析一個 Uri 對象:

var uri = Uri.parse('http://example.org:8080/foo/bar#frag');

assert(uri.scheme   == 'http');
assert(uri.host     == 'example.org');
assert(uri.path     == '/foo/bar');
assert(uri.fragment == 'frag');
assert(uri.origin   == 'http://example.org:8080');

Building URIs

使用 Uri() 構造函數可以從 URI 的 各個部分來構造一個 Uri 對象:

var uri = new Uri(scheme: 'http', host: 'example.org',
                  path: '/foo/bar', fragment: 'frag');
assert(uri.toString() ==
    'http://example.org/foo/bar#frag');

Dates and times

DateTime 對象代表某個時刻。時區是 UTC 或者 本地時區。

一些構造函數可以創建 DateTime 對象:

// Get the current date and time.
var now = new DateTime.now();

// Create a new DateTime with the local time zone.
var y2k = new DateTime(2000);   // January 1, 2000

// Specify the month and day.
y2k = new DateTime(2000, 1, 2); // January 2, 2000

// Specify the date as a UTC time.
y2k = new DateTime.utc(2000);   // 1/1/2000, UTC

// Specify a date and time in ms since the Unix epoch.
y2k = new DateTime.fromMillisecondsSinceEpoch(
    946684800000, isUtc: true);

// Parse an ISO 8601 date.
y2k = DateTime.parse('2000-01-01T00:00:00Z');
millisecondsSinceEpoch 屬性返回自從 “Unix epoch”—January 1, 1970, UTC 以來的毫秒數值:

// 1/1/2000, UTC
y2k = new DateTime.utc(2000);
assert(y2k.millisecondsSinceEpoch == 946684800000);

// 1/1/1970, UTC
var unixEpoch = new DateTime.utc(1970);
assert(unixEpoch.millisecondsSinceEpoch == 0);

使用 Duration 類可以計算兩個日期之間的間隔, 還可以前后位移日期:

var y2k = new DateTime.utc(2000);

// Add one year.
var y2001 = y2k.add(const Duration(days: 366));
assert(y2001.year == 2001);

// Subtract 30 days.
var december2000 = y2001.subtract(
    const Duration(days: 30));
assert(december2000.year == 2000);
assert(december2000.month == 12);

// Calculate the difference between two dates.
// Returns a Duration object.
var duration = y2001.difference(y2k);
assert(duration.inDays == 366); // y2k was a leap year.

警告: 使用 Duration 來在 DateTime 對象上前后移動數天可能會有問題, 比如像夏令時等時間問題。如果要按照天數來位移時間,則 需要使用 UTC 日期。

Utility classes(工具類)

核心庫還包含了一些常用的工具類,比如排序、 值映射以及遍歷數據等。

Comparing objects(比較對象)

實現 Comparable 接口表明該對象可以相互比較,通常用來 排序。compareTo() 函數對于 小于的值返回 < 0 ; 相同的值返回 0 ; 大于的值返回 > 0、

class Line implements Comparable {
  final length;
  const Line(this.length);
  int compareTo(Line other) => length - other.length;
}

main() {
  var short = const Line(1);
  var long = const Line(100);
  assert(short.compareTo(long) < 0);
}

Implementing map keys

Dart 中的每個對象都有一個整數 hash 碼,這樣每個對象都 可以當做 map 的 key 來用。但是,你可以覆寫 hashCode getter 來自定義 hash 碼的實現,如果你這樣做了,你也需要 同時覆寫 == 操作符。相等的對象(使用 == 比較)的 hash 碼應該一樣。Hasm 碼并不要求是唯一的, 但是應該具有良好的分布形態。

class Person {
  final String firstName, lastName;

  Person(this.firstName, this.lastName);

  // Override hashCode using strategy from Effective Java,
  // Chapter 11.
  int get hashCode {
    int result = 17;
    result = 37 * result + firstName.hashCode;
    result = 37 * result + lastName.hashCode;
    return result;
  }

  // You should generally implement operator == if you
  // override hashCode.
  bool operator ==(other) {
    if (other is! Person) return false;
    Person person = other;
    return (person.firstName == firstName &&
        person.lastName == lastName);
  }
}

main() {
  var p1 = new Person('bob', 'smith');
  var p2 = new Person('bob', 'smith');
  var p3 = 'not a person';
  assert(p1.hashCode == p2.hashCode);
  assert(p1 == p2);
  assert(p1 != p3);
}

Iteration

Iterable 和 Iterator 類支持 for-in 循環。當你創建一個類的時候,繼承或者實現 Iterable 可以 提供一個用于 for-in 循環的 Iterators。 實現 Iterator 來定義實際的遍歷操作。

class Process {
  // Represents a process...
}

class ProcessIterator implements Iterator<Process> {
  Process current;
  bool moveNext() {
    return false;
  }
}

// A mythical class that lets you iterate through all
// processes. Extends a subclass of Iterable.
class Processes extends IterableBase<Process> {
  final Iterator<Process> iterator =
      new ProcessIterator();
}

main() {
  // Iterable objects can be used with for-in.
  for (var process in new Processes()) {
    // Do something with the process.
  }
}

Exceptions

Dart 核心庫定義了很多常見的異常和錯誤類。 異常通常是一些可以預知和預防的例外情況。 錯誤這是無法預料的并且不需要預防的情況。

下面是一些常見的錯誤:

NoSuchMethodError 當在一個對象上調用一個該對象沒有 實現的函數會拋出該錯誤。

ArgumentError 調用函數的參數不合法會拋出這個錯誤。

拋出一個應用相關的異常是一種用來表明有錯誤發生的常見 方法。你還可以通過實現 Exception 接口來自定義 一些異常。

class FooException implements Exception {
  final String msg;
  const FooException([this.msg]);
  String toString() => msg ?? 'FooException';
}

dart:async - asynchronous programming

異步編程通常使用回調函數,但是 Dart 提供了另外的 選擇: Future 和 Stream 對象。 Future 和 JavaScript 中的 Promise 類似,代表在將來某個時刻會返回一個 結果。Stream 是一種用來獲取一些列數據的方式,例如 事件流。 Future, Stream, 以及其他異步操作的類在 dart:async 庫中。

注意: 你并不是都需要直接使用 Future 和 Stream。 Dart 語言有一些異步功能的關鍵字,例如 async 和 await。 詳細信息 請參考 異步支持。

dart:async 庫在 web app 和命令行 app 都可以使用。 只需要導入 dart:async 即可使用:

import 'dart:async';

Future

在 Dart 庫中隨處可見 Future 對象,通常異步函數返回的對象就是一個 Future。 當一個 future 執行完后,他里面的值 就可以使用了。

Using await

在直接使用 Future api 之前,你可以考慮先使用 await。 使用 await 的表達式比直接使用 Future api 的代碼要更加 容易理解。

例如下面的方法。使用 Future 的 then() 函數來 執行三個異步方法, 每個方法執行完后才繼續執行后一個方法。

runUsingFuture() {
  //...
  findEntrypoint().then((entrypoint) {
    return runExecutable(entrypoint, args);
  }).then(flushThenExit);
}

下面是使用 await 表達式實現的同樣功能的代碼, 看起來更像是同步代碼,更加容易理解:

runUsingAsyncAwait() async {
  //...
  var entrypoint = await findEntrypoint();
  var exitCode = await runExecutable(entrypoint, args);
  await flushThenExit(exitCode);
}

異步方法可以把 Future 中的錯誤當做 異常來處理。 例如:

attached() async {
  super.attached();
  try {
    await appObject.start();
  } catch (e) {
    //...handle the error...
  }
}

重要: 異步方法(帶有關鍵字 async 的方法)會返回 Future。 如果你不想讓你的方法返回 future,則 不要使用 async 關鍵字。 例如,你可以在你的方法里面調用一個 異步方法。

關于使用 await 和相關 Dart 語言的其他特性,請參考 異步支持。

Basic usage

可以使用 then() 來在 future 完成的時候執行其他代碼。例如 HttpRequest.getString() 返回一個Future,由于 HTTP 請求是一個 耗時操作。使用 then() 可以在 Future 完成的時候執行其他代碼 來解析返回的數據:

HttpRequest.getString(url).then((String result) {
  print(result);
});
// Should handle errors here.

使用 catchError() 來處理 Future 對象可能拋出的 各種異常和錯誤:

HttpRequest.getString(url).then((String result) {
  print(result);
}).catchError((e) {
  // Handle or ignore the error.
});

then().catchError() 模式就是異步版本的 try-catch

重要: 確保是在 then() 返回的 Future 上調用 catchError(), 而不是在 原來的 Future 對象上調用。否則的話,catchError() 就只能處理原來 Future 對象拋出的異常而無法處理 then() 代碼 里面的異常。

Chaining multiple asynchronous methods

then() 函數返回值為 Future,可以把多個異步調用給串聯起來。 如果 then() 函數注冊的回調函數也返回一個 Future,而 then() 返回一個同樣的 Future。如果回調函數返回的是一個其他類型的值, 則 then() 會創建一個新的 Future 對象 并完成這個 future。

Future result = costlyQuery();

return result.then((value) => expensiveWork())
             .then((value) => lengthyComputation())
             .then((value) => print('done!'))
             .catchError((exception) => print('DOH!'));

上面的示例中,代碼是按照如下順序執行的:

    1. costlyQuery()
    1. expensiveWork()
    1. lengthyComputation()

Waiting for multiple futures

有時候,你的算法要求調用很多異步方法,并且等待 所有方法完成后再繼續執行。使用 Future.wait() 這個靜態函數來管理多個 Future 并等待所有 Future 執行完成。

Future deleteDone = deleteLotsOfFiles();
Future copyDone = copyLotsOfFiles();
Future checksumDone = checksumLotsOfOtherFiles();

Future.wait([deleteDone, copyDone, checksumDone])
    .then((List values) {
      print('Done with all the long steps');
    });

Stream

Stream 在 Dart API 中也經常出現,代表一些列數據。 例如, HTML 按鈕點擊事件就可以使用 stream 來表示。 還可以把讀取文件內容當做一個 Stream。

Using an asynchronous for loop

有時候可以使用異步 for 循環(await for)來 替代 Stream API。

例如下面的示例中,使用 Stream 的 listen() 函數來訂閱 一些文件,然后使用一個方法參數來 搜索每個文件和目錄。

void main(List<String> arguments) {
  ...
  FileSystemEntity.isDirectory(searchPath).then((isDir) {
    if (isDir) {
      final startingDir = new Directory(searchPath);
      startingDir
          .list(
              recursive: argResults[RECURSIVE],
              followLinks: argResults[FOLLOW_LINKS])
          .listen((entity) {
        if (entity is File) {
          searchFile(entity, searchTerms);
        }
      });
    } else {
      searchFile(new File(searchPath), searchTerms);
    }
  });
}

下面是使用 await 表達式和異步 for 循環 實現的等價的代碼, 看起來更像是同步代碼:

main(List<String> arguments) async {
  ...
  if (await FileSystemEntity.isDirectory(searchPath)) {
    final startingDir = new Directory(searchPath);
    await for (var entity in startingDir.list(
        recursive: argResults[RECURSIVE],
        followLinks: argResults[FOLLOW_LINKS])) {
      if (entity is File) {
        searchFile(entity, searchTerms);
      }
    }
  } else {
    searchFile(new File(searchPath), searchTerms);
  }
}

重要: 在使用 await for 之前請確保使用之后的代碼看起來確實是 更加清晰易懂了。例如,對于 DOM時間監聽器通常 不會使用 await for,原因在于 DOM 發送 無盡的事件流。 如果使用 await for 來在同一行注冊兩個 DOM 事件監聽器, 則第二個事件在不會被處理。

關于使用 await 和相關 Dart 語言的其他特性, 請參考 異步支持。

Listening for stream data

要想在每個數據到達的時候就去處理,則可以選擇使用 await for 或者 使用 listen() 函數來訂閱事件:

// Find a button by ID and add an event handler.
querySelector('#submitInfo').onClick.listen((e) {
  // When the button is clicked, it runs this code.
  submitData();
});

上面的示例中, onClick 屬性是 “submitInfo” 按鈕提供的一個 Stream 對象。

如果你只關心里面其中一個事件,則可以使用這些屬性: first, last, 或者 single。要想在處理事件之前先測試是否滿足條件,則 使用 firstWhere(), lastWhere(), 或者 singleWhere() 函數。

如果你關心一部分事件,則可以使用 skip(), skipWhile(), take(), takeWhile(), 和 where() 這些函數。

Transforming stream data

經常你需要先轉換 stream 里面的數據才能使用。 使用 transform() 函數可以生產另外一個數據類型 的 Stream 對象:

var stream = inputStream
    .transform(UTF8.decoder)
    .transform(new LineSplitter());

上面的代碼使用兩種轉換器(transformer)。第一個使用 UTF8.decoder 來把整數類型的數據流轉換為字符串類型的數據流。然后使用 LineSplitter 把字符串類型數據流轉換為按行分割的數據流。 這些轉換器都來至于 dart:convert 庫。 參考 dart:convert ) 了解詳情。

Handling errors and completion

使用異步 for 循環 (await for) 和使用 Stream API 的 異常處理情況是有 區別的。

如果是異步 for 循環,則可以 使用 try-catch 來處理異常。

在 stream 關閉后執行的代碼位于異步 for 循環 之后。

readFileAwaitFor() async {
  var config = new File('config.txt');
  Stream<List<int>> inputStream = config.openRead();

  var lines = inputStream
      .transform(UTF8.decoder)
      .transform(new LineSplitter());
  try {
    await for (var line in lines) {
      print('Got ${line.length} characters from stream');
    }
    print('file is now closed');
  } catch (e) {
    print(e);
  }
}

如果你使用 Stream API,則需要 使用 onError 函數來處理異常。 stream 完成后執行的代碼要通過 onDone 函數 來執行。

var config = new File('config.txt');
Stream<List<int>> inputStream = config.openRead();

inputStream
    .transform(UTF8.decoder)
    .transform(new LineSplitter())
    .listen((String line) {
      print('Got ${line.length} characters from stream');
    }, onDone: () {
      print('file is now closed');
    }, onError: (e) {
      print(e);
    });

More information

關于在命令行應用中使用 Future 和 Stream 的更多示例,請參考 dart:io 里面的內容。 下面也是一些可以參考的文章和教程:

Asynchronous Programming: Futures

  • Futures and Error Handling
  • The Event Loop and Dart
  • Asynchronous Programming: Streams
  • Creating Streams in Dart

dart:math - math and random

Math 庫提供了常見的數學運算功能,例如 sine 和 cosine, 最大值、最小值等,還有各種常量 例如 pi 和 e 等。Math 庫中 的大部分函數都是頂級方法。

導入 dart:math 就可以使用 Math 庫了。 下面的示例代碼使用前綴 math 來引用庫中的頂級 方法和常量:

import 'dart:math' as math;

Trigonometry

Math 庫中提供了常見的三角運算功能:

// Cosine
assert(math.cos(math.PI) == -1.0);

// Sine
var degrees = 30;
var radians = degrees * (math.PI / 180);
// radians is now 0.52359.
var sinOf30degrees = math.sin(radians);
// sin 30° = 0.5
assert((sinOf30degrees - 0.5).abs() < 0.01);

注意: 上面這些函數是基于弧度的不是基于角度的。

Maximum and minimum

Math 庫提供了 max() 和 min() 函數用來計算最大值和最小值:

assert(math.max(1, 1000) == 1000);
assert(math.min(1, -1000) == -1000);

Math constants

Math 庫中提供各種數學常量,例如 pi, e 等。

// See the Math library for additional constants.
print(math.E);     // 2.718281828459045
print(math.PI);    // 3.141592653589793
print(math.SQRT2); // 1.4142135623730951

Random numbers

使用 Random 類可以生成隨機數。 在 Random 構造函數中還可以提供一個隨機種子:

var random = new math.Random();
random.nextDouble(); // Between 0.0 and 1.0: [0, 1)
random.nextInt(10);  // Between 0 and 9.

也可以生成隨機的布爾值:

var random = new math.Random();
random.nextBool();  // true or false

More information

詳細的信息可以參考 Math API 文檔 來了解。 還可以參考下面這些類的 API 文檔 num, int, 和 double。

dart:html - browser-based apps

如果要和瀏覽器打交道則需要使用 dart:html 庫, 訪問 DOM 元素和使用 HTML5 API。 DOM 是 Document Object Model 的縮寫,用來 描述 HTML 頁面的結構。

dart:html 還可以用來操作樣式表(CSS)、用 HTTP 請求 來獲取數據,使用 WebSockets 來獲取數據。 HTML5 (和 dart:html) 具有很多其他的 API 在這里并沒有介紹。 只有 Web 應用可以使用 dart:html,命令行應用無法使用該庫。

注意: 關于構建 Web 應用的更高層級的框架,請參考 Polymer Dart 和 Angular 2 for Dart。

在 web 應用中導入 dart:html 就可以使用 HTML 相關的功能了:

import 'dart:html';

Manipulating the DOM

要使用 DOM 你需要了解 windows, documents, elements, 和 nodes 等概念。

一個 Window 對象代表 瀏覽器實際的窗口。每個窗口都有一個文檔(Document)對象, 文檔對象是當前正在加載的界面。Window 對象還可以訪問各種 API,例如 用于存儲數據的 IndexedDB、用于動畫的 requestAnimationFrame 等。 在多窗口瀏覽器中,每個窗口(tab 也)都有 自己的 Window 對象。

使用 Document 對象, 可以創建和操縱 document 中的 Elements 對象。 注意 Document 本身也是一個 element,也是可以 被修改的。

DOM 模型是很多 Nodes 組成的樹狀結構。這些 nodes 通常 是 elements,但是也可以是 attributes、 text、 comments、 和其他 DOM 類型。 除了跟節點沒有父節點以外,其他 DOM 中的節點都有一個 父節點,還有可能帶有很多子節點。

Finding elements

在操作一個 element 之前,你需要先找到這個 element。 使用查詢語法可以查找所需要的 element。

使用頂級方法 querySelector() 和 querySelectorAll() 可以查找一個或者多個符合條件的 element。可以根據 ID、class、tag、name 或者 這些的組合來查詢 element。 CSS 選擇器 規范 定義了選擇器的形式, 例如使用 # 前綴代表 ID,英文句號 (.) 代表 classes。

使用 querySelector() 方法可以獲取第一個符合選擇器要求的元素; 而 querySelectorAll() 返回所有符合 選擇器要求的元素結合。

// Find an element by id (an-id).
Element elem1 = querySelector('#an-id');

// Find an element by class (a-class).
Element elem2 = querySelector('.a-class');

// Find all elements by tag (<div>).
List<Element> elems1 = querySelectorAll('div');

// Find all text inputs.
  List<Element> elems2 =
      querySelectorAll('input[type="text"]');

// Find all elements with the CSS class 'class'
// inside of a <p> that is inside an element with
// the ID 'id'.
List<Element> elems3 = querySelectorAll('#id p.class');

Manipulating elements

可以使用屬性(properties)來修改 element 的狀態。 Node 和子類型 Element 定義了所有 element 都具有的屬性。例如, 所有 element 都有 classes, hidden, id, style, 和 title 屬性,你可以使用這些屬性來修改 element 的狀態。 Element 的 子類還定義了其他屬性,比如 AnchorElement 定義了 href 屬性。

例如下面的示例在 HTML 中設置一個錨鏈接:

<a id="example" href="http://example.com">link text</a>

<a>標簽使用 href 定義了一個 element 和一個包含文字 “linktext” 的 text node(使用 text 屬性訪問)。使用 AnchorElement 的 href 屬性 可以修改點擊該鏈接跳轉的地址:

querySelector('#example').href = 'http://dartlang.org';

通常你需要在多個 element 上設置屬性。例如,下面的示例在 所有 class 樣式帶有 “mac”, “win”, 或者 “linux” 的 element 上設置 hidden 屬性。設置 hidden 屬性為 true 和 設置 CSS 樣式 display:none 是同樣的效果。

<!-- In HTML: -->
<p>
  <span class="linux">Words for Linux</span>
  <span class="macos">Words for Mac</span>
  <span class="windows">Words for Windows</span>
</p>

// In Dart:
final osList = ['macos', 'windows', 'linux'];

// In real code you'd programmatically determine userOs.
var userOs = 'linux';

for (var os in osList) { // For each possible OS...
  bool shouldShow = (os == userOs); // Matches user OS?

  // Find all elements with class=os. For example, if
  // os == 'windows', call querySelectorAll('.windows')
  // to find all elements with the class "windows".
  // Note that '.$os' uses string interpolation.
  for (var elem in querySelectorAll('.$os')) {
    elem.hidden = !shouldShow; // Show or hide.
  }
}

當屬性不能訪問或者不方便訪問的時候,可以使用 Element 的 attributes 屬性。 這個屬性是一個 Map<String, String>,里面的 key 為屬性名字。所有 HTML 元素的 屬性名字以及意義,請參考 MDN Attributes 網頁。下面是一個設置 屬性值的示例:

elem.attributes['someAttribute'] = 'someValue';

Creating elements

還可以創建新的 element 然后添加到 HTML 頁面的 DOM 中。下面的示例創建了一個 段落 (

) 元素:

var elem = new ParagraphElement();
elem.text = 'Creating is easy!';

使用 HTML 文本也可以創建 element。所包含的子元素 也一起被創建:

var elem2 =
    new Element.html('<p>Creating <em>is</em> easy!</p>');

注意上面的 elem2 對象是一個 ParagraphElement 。

給新創建的 Element 指定一個父節點可以把這個 Element 添加到 DOM 中。 可以把 Element 添加到任何已經存在于 DOM 中的其他 Element 的 children 中。 例如,下面的示例,body 是一個 element,使用 children 屬性來 訪問該元素的所有子元素(返回的是一個 List),然后把新的 elem2 添加 到子元素集合中。

document.body.children.add(elem2);

Adding, replacing, and removing nodes

之前說過,element 也是 node 的一種。使用 Node 的 nodes 屬性可以 獲取到當前 node 的所有子元素,nodes 返回的是 List ( children 屬性只包含 Element 類型的 nodes)。 獲取到這個 Node list 后,就可以使用 List 的各種函數來 處理這些 Node 對象了。

使用 List 的add() 函數可以把一個 node 添加到所有子元素的 最后:

// Find the parent by ID, and add elem as its last child.
querySelector('#inputs').nodes.add(elem);

使用 Node 的 replaceWith() 函數可以替換一個 Node:

// Find a node by ID, and replace it in the DOM.
querySelector('#status').replaceWith(elem);

使用 Node 的 remove() 函數來刪除 node:

// Find a node by ID, and remove it from the DOM.
querySelector('#expendable').remove();

Manipulating CSS styles

CSS(cascading style sheets 的縮寫)定義了 DOM 元素的 UI 樣式。 在一個 element 上附加 ID 和 class 屬性可以修改 其應用的 CSS 樣式。

沒有 element 都有一個 classes 屬性(field),該屬性的類型為 List。 添加和移除上面的 CSS 類就是向這個集合中添加和刪除字符串。 流入,下面的示例中給 element 添加了 warning CSS 類樣式。

var element = querySelector('#message');
element.classes.add('warning');

通過 ID 來查找元素非常高效。通過 id 屬性你可以動態給一個 Element 指定 一個 ID 值。

var message = new DivElement();
message.id = 'message2';
message.text = 'Please subscribe to the Dart mailing list.';

使用級聯調用可以減少 需要編寫的代碼:

var message = new DivElement()
    ..id = 'message2'
    ..text = 'Please subscribe to the Dart mailing list.';

使用 ID 和 CSS 的 classes 來應用樣式是最佳的方式,但是有時候 你還是希望直接在 element 上應用具體的樣式,則 可以直接使用 style 屬性:

message.style
    ..fontWeight = 'bold'
    ..fontSize = '3em';

Handling events

要響應像點擊、聚焦等外部事件,你需要使用事件監聽器。 在頁面上的任何 element 上都可以注冊事件監聽器。 事件分發和傳遞是一個很復雜的議題: 如果你是 Web 開發新手, 請到 這里來 詳細研究這個事件分發機制。

使用 element.onEvent.listen(function)來添加事件監聽器, 這里的 Event 是事件的名字,而 function 是事件處理器。

例如,下面是處理按鈕點擊的事件:

// Find a button by ID and add an event handler.
querySelector('#submitInfo').onClick.listen((e) {
  // When the button is clicked, it runs this code.
  submitData();
});

事件可以通過 DOM 樹來向上或者向下傳遞。 通過 e.target 可以獲取是那個 element 觸發該事件的:

document.body.onClick.listen((e) {
  var clickedElem = e.target;
  print('You clicked the ${clickedElem.id} element.');
});

要查看所有可以注冊的事件名字,可以查看 Element 文檔中的 “onEventType” 屬性。 下面是一些常見的事件:

  • change
  • blur
  • keyDown
  • keyUp
  • mouseDown
  • mouseUp

Using HTTP resources with HttpRequest

HttpRequest 類是之前 大家耳熟能詳的 XMLHttpRequest 的功能一樣,使用該類 可以在 web 應用總訪問 HTTP 資源。 一般而言, AJAX 風格的應用會很依賴 HttpRequest。使用 HttpRequest 來 動態的加載 JSON 數據或者其他資源。 還可以動態的向服務器發送數據。

下面的示例假設所有的資源都是來至于和當前腳本文件位于 同一個 web 服務器。由于瀏覽器的安全限制, HttpRequest 要使用 其他服務器的資源是比較麻煩的。如果你需要訪問 其他服務器上的資源, 你需要使用 JSONP 技術或者 啟用另外一個資源服務器的 CORS header。

Getting data from the server HttpRequest 的靜態函數 getString() 可以很方便的從服務器獲取資源。 使用 await 來確保 getString() 調用資源返回后再 在繼續執行后面的代碼:

import 'dart:html';
import 'dart:async';

// A JSON-formatted file next to this page.
var uri = 'data.json';

main() async {
  // Read a JSON file.
  var data = await HttpRequest.getString(uri);
  processString(data);
}

processString(String jsonText) {
  parseText(jsonText);
}

dart:convert 中會介紹 JSON API 相關的內容。

使用 try-catch 來指定異常處理代碼:

try {
  data = await HttpRequest.getString(jsonUri);
  processString(data);
} catch (e) {
  handleError(e);
}
// ...
handleError(error) {
  print('Uh oh, there was an error.');
  print(error.toString());
}

如果除了返回的文本數據以外你還需要 訪問 HttpRequest,你可以使用 request() 靜態函數。下面 是一個讀取 XML 數據的示例:

import 'dart:html';
import 'dart:async';

// An XML-formatted file next to this page.
var xmlUri = 'data.xml';

main() async {
  // Read an XML file.
  try {
    var data = await HttpRequest.request(xmlUri);
    processRequest(data);
  } catch (e) {
    handleError(e);
  }
}

processRequest(HttpRequest request) {
  var xmlDoc = request.responseXml;
  try {
    var license = xmlDoc.querySelector('license').text;
    print('License: $license');
  } catch (e) {
    print("$xmlUri doesn't have correct XML formatting.");
  }
}

可以使用 full API 來處理各種情況。例如, 設置請求 header 信息。

下面是使用 HttpRequest full API 的常用流程:

    1. 創建 HttpRequest 對象。
    1. 使用 GET 或者 POST 打開一個 URL。
    1. 添加事件處理器。
    1. 發送請求。

例如:

import 'dart:html';
// ...
var request = new HttpRequest()
    ..open('POST', dataUrl)
    ..onLoadEnd.listen((_) => requestComplete(request))
    ..send(encodedData);

Sending data to the server

HttpRequest 還可以使用 HTTP POST 函數來向服務器發送數據。 例如,你可能希望動態的提交數據到服務器。 向 RESTful web 服務器發送 JSON 數據是一種非常常見的情況。

在表單處理中提交數據需要提供一個 name-value 數據對,該數據還需要 使用 URI 編碼(關于 URI 類的信息請 參考 URIs 文檔)。 如果要在表單處理器中提交數據則還 需要設置 Content-type header 為 application/x-www-form-urlencode。

import 'dart:html';

String encodeMap(Map data) {
  return data.keys.map((k) {
    return '${Uri.encodeComponent(k)}=' +
           '${Uri.encodeComponent(data[k])}';
  }).join('&');
}

loadEnd(HttpRequest request) {
  if (request.status != 200) {
    print('Uh oh, error: ${request.status}');
  } else {
    print('Data has been posted');
  }
}

main() async {
  var dataUrl = '/registrations/create';
  var data = {'dart': 'fun', 'editor': 'productive'};
  var encodedData = encodeMap(data);

  var httpRequest = new HttpRequest();
  httpRequest.open('POST', dataUrl);
  httpRequest.setRequestHeader(
      'Content-type',
      'application/x-www-form-urlencoded');
  httpRequest.send(encodedData);
  await httpRequest.onLoadEnd.first;
  loadEnd(httpRequest);
}

Sending and receiving real-time data with WebSockets

WebSocket 可以讓你的 web 應用和服務器持續的交互數據,不用 一直的輪詢。創建 WebSocket 的服務器會監聽 ws:// 開頭的 URL, 例如 ws://127.0.0.1:1337/ws。 通過 WebSocket 發送的數據可以是字符串或者 blob。 通常都是使用 JSON 格式的字符串。

要在 web 應用中使用 WebSocket,需要先創建 WebSocket 對象,把 WebSocket URL 作為該對象的參數。

var ws = new WebSocket('ws://echo.websocket.org');

Sending data

使用 send() 函數向 WebSocket 發送數據:

ws.send('Hello from Dart!');

Receiving data

要從 WebSocket 接收數據,需要注冊一個事件 監聽器:

ws.onMessage.listen((MessageEvent e) {
  print('Received message: ${e.data}');
});

消息事件處理函數的參數為 MessageEvent 對象。 該對象的 data 變量保存了服務器返回的數據。

Handling WebSocket events

你的應用可以處理如下的 WebSocket 事件:open, close, error, 和 (前面演示的) message。下面是演示各種事件 的 示例:

void initWebSocket([int retrySeconds = 2]) {
  var reconnectScheduled = false;

  print("Connecting to websocket");
  ws = new WebSocket('ws://echo.websocket.org');

  void scheduleReconnect() {
    if (!reconnectScheduled) {
      new Timer(
          new Duration(milliseconds: 1000 * retrySeconds),
          () => initWebSocket(retrySeconds * 2));
    }
    reconnectScheduled = true;
  }

  ws.onOpen.listen((e) {
    print('Connected');
    ws.send('Hello from Dart!');
  });

  ws.onClose.listen((e) {
    print('Websocket closed, retrying in ' +
          '$retrySeconds seconds');
    scheduleReconnect();
  });

  ws.onError.listen((e) {
    print("Error connecting to ws");
    scheduleReconnect();
  });

  ws.onMessage.listen((MessageEvent e) {
    print('Received message: ${e.data}');
  });
}

More information

上面只是簡單的介紹了 dart:html 庫。更多信息 請參考 dart:html。 Dart 還有一些 web 特殊領域的 api,例如 web audio, IndexedDB, 和 WebGL。

dart:io - I/O for command-line apps

dart:io 庫 提供了一些和 文件、目錄、進程、sockets、 WebSockets、和 HTTP 客戶端以及服務器的 API。 只有命令行應用可以使用 dart:io 庫,web app 無法使用。

一般而言,dart:io 庫實現和提供的是異步 API。 同步函數很容易阻塞應用,后期擴展起來非常麻煩。 因此,大部分的操作返回值都是 Future 或者 Stream 對象, 如果你熟悉 Node.js 則對這種 模式會有所了解。

dart:io 里面也有一小部分同步方法,這些方法都使用 sync 前綴命名方法名字。 這里就不再介紹這些同步方法了。

注意: 只有命令行應用才能導入 dart:io。

Files and directories

I/O 庫可以讓命令行應用讀寫文件和查看目錄。 讀取文件有兩種方式:一次讀完或者通過流的方式來讀取。 一次讀完需要把文件內容讀到內存中,如果文件 非常大或者你希望一邊讀文件一邊處理,則應該 使用 Stream, 在 流式讀取文件中介紹。

Reading a file as text

對于編碼為 UTF-8 的文本,可以使用函數 readAsString() 一次性 的讀取整個文本。如果單行文字比較重要,則可以 使用 readAsLines() 來讀取。 這兩個函數返回一個 Future 對象,當文件 讀取完的時候,可以從 Future 對象獲取一個或者多個字符串。

import 'dart:io';

main() async {
  var config = new File('config.txt');
  var contents;

  // Put the whole file in a single string.
  contents = await config.readAsString();
  print('The entire file is ${contents.length} characters long.');

  // Put each line of the file into its own string.
  contents = await config.readAsLines();
  print('The entire file is ${contents.length} lines long.');
}

Reading a file as binary

下面的示例把文件數據讀取為字節流。 同樣 readAsBytes() 函數返回值為 Future, 當讀完文件后,可以從 Future 中獲取數據。

import 'dart:io';

main() async {
  var config = new File('config.txt');

  var contents = await config.readAsBytes();
  print('The entire file is ${contents.length} bytes long');
}

Handling errors

在 Future 上注冊一個 catchError 來處理異常, 還可以在 async 方法中使用 try-catch 來 處理異常:

import 'dart:io';

main() async {
  var config = new File('config.txt');
  try {
    var contents = await config.readAsString();
    print(contents);
  } catch (e) {
    print(e);
  }
}

Streaming file contents

使用 Stream 讀取文件的時候, 使用 Stream API 或者 await for 可以一點點的讀取, 詳情參考 異步支持。

import 'dart:io';
import 'dart:convert';
import 'dart:async';

main() async {
  var config = new File('config.txt');
  Stream<List<int>> inputStream = config.openRead();

  var lines = inputStream
      .transform(UTF8.decoder)
      .transform(new LineSplitter());
  try {
    await for (var line in lines) {
      print('Got ${line.length} characters from stream');
    }
    print('file is now closed');
  } catch (e) {
    print(e);
  }
}

Writing file contents

使用 IOSink 可以往文件 寫入內容。使用 File 的 openWrite() 函數獲取到一個 IOSink。 默認的寫模式為 FileMode.WRITE,新寫入的數據會完全覆蓋 文件之前的內容。

var logFile = new File('log.txt');
var sink = logFile.openWrite();
sink.write('FILE ACCESSED ${new DateTime.now()}\n');
sink.close();

如果想在文件末尾追加內容,則可以使用 mode 可選參數,參數取值 為 FileMode.APPEND:

var sink = logFile.openWrite(mode: FileMode.APPEND);
使用 add(List<int> data) 函數可以寫二進制數據到文件。

Listing files in a directory

查找目錄中的所有文件和子目錄是一個異步操作。 list() 函數返回一個 Stream,當遇到文件或者子目錄的時候, Stream 就發射一個對象。

import 'dart:io';

main() async {
  var dir = new Directory('/tmp');

  try {
    var dirList = dir.list();
    await for (FileSystemEntity f in dirList) {
      if (f is File) {
        print('Found file ${f.path}');
      } else if (f is Directory) {
        print('Found dir ${f.path}');
      }
    }
  } catch (e) {
    print(e.toString());
  }
}

Other common functionality

File 和 Directory 類包含其他的一些文件操作, 下面只是一些常見的函數:

  • 創建文件或者目錄: create() in File and Directory
  • 刪除文件或者目錄: delete() in File and Directory
  • 獲取文件的長度: length() in File
  • 隨機位置訪問文件: open() in File

參考 File 和 Directory 的 API 文檔來 查看所有的函數。

HTTP clients and servers

dart:io 庫提供了一些命令行應用可以用來訪問 HTTP 資源 和運行 HTTP 服務器的類。

HTTP server

HttpServer 類 提供了用來建構 Web 服務器的底層方法。可以匹配 請求處理、設置 header、處理數據等。

下面的示例項目只能返回簡單的文本信息。 服務器監聽本機地址 127.0.0.1 的 8888 端口, 響應來自于 /languages/dart 路徑的請求。所有其他的 請求都有默認的請求處理器處理(返回 404 頁面沒發現的錯誤提示)。

import 'dart:io';

main() async {
  dartHandler(HttpRequest request) {
    request.response.headers.contentType =
        new ContentType('text', 'plain');
    request.response.write('Dart is optionally typed');
    request.response.close();
  }

  var requests = await HttpServer.bind('127.0.0.1', 8888);
  await for (var request in requests) {
    print('Got request for ${request.uri.path}');
    if (request.uri.path == '/languages/dart') {
      dartHandler(request);
    } else {
      request.response.write('Not found');
      request.response.close();
    }
  }
}

HTTP client

HttpClient 類可以 在命令行應用程序或者服務器應用程序中使用,用來請求 HTTP 資源。 可以設置請求頭、HTTP 請求方式和讀寫 數據。HttpClient 無法在 web 應用中使用。 在 web 應用中可以使用 HttpRequest class。 下面是使用 HttpClient 的一個示例:

import 'dart:io';
import 'dart:convert';

main() async {
  var url = Uri.parse(
      'http://127.0.0.1:8888/languages/dart');
  var httpClient = new HttpClient();
  var request = await httpClient.getUrl(url);
  print('have request');
  var response = await request.close();
  print('have response');
  var data = await response.transform(UTF8.decoder).toList();
  var body = data.join('');
  print(body);
  httpClient.close();
}

More information

除了上面提到的幾個功能外,dart:io 庫還包含 processes, sockets, 和 web sockets 等相關的 API。

dart:convert - decoding and encoding JSON, UTF-8, and more

dart:convert 庫 里面有一些用來轉換 JSON 和 UTF-8 的轉換器,還可以自定義 新的轉換器。 JSON 是非常流行的數據格式。 UTF-8 是一種非常流行的編碼格式, 能夠代表所有 Unicode 字符 集。

命令行應用和 web 應用都可以使用 dart:convert 庫。 導入 dart:convert 就可以使用該庫了。

Decoding and encoding JSON

使用 JSON.decode() 函數把 JSON 字符串解碼為 Dart 對象:

import 'dart:convert' show JSON;

main() {
  // NOTE: Be sure to use double quotes ("),
  // not single quotes ('), inside the JSON string.
  // This string is JSON, not Dart.
  var jsonString = '''
  [
    {"score": 40},
    {"score": 80}
  ]
  ''';

  var scores = JSON.decode(jsonString);
  assert(scores is List);

  var firstScore = scores[0];
  assert(firstScore is Map);
  assert(firstScore['score'] == 40);
}

使用 JSON.encode() 可以把 Dart 對象 編碼為 JSON 字符串:

import 'dart:convert' show JSON;

main() {
  var scores = [
    {'score': 40},
    {'score': 80},
    {'score': 100, 'overtime': true, 'special_guest': null}
  ];

  var jsonText = JSON.encode(scores);
  assert(jsonText == '[{"score":40},{"score":80},'
                     '{"score":100,"overtime":true,'
                     '"special_guest":null}]');
}

默認只支持 int、double、String、bool、null、List或者 Map(key 需要為 string) 這些類型轉換為 JSON。 集合對象會使用遞歸的形式來轉換每個對象。

對于默認不支持的對象,可以有兩種選擇: 一,調用 encode() 并指定第二個參數, 該參數是一個函數用來返回一個默認支持的對象; 二,不指定第二個參數,則會 調用該對象的 toJson() 函數。

Decoding and encoding UTF-8 characters

使用 UTF8.decode() 來解碼 UTF8-encoded 字節流為 Dart 字符串:

import 'dart:convert' show UTF8;

main() {
  var string = UTF8.decode([
    0xc3, 0x8e, 0xc3, 0xb1, 0xc5, 0xa3, 0xc3, 0xa9,
    0x72, 0xc3, 0xb1, 0xc3, 0xa5, 0xc5, 0xa3, 0xc3,
    0xae, 0xc3, 0xb6, 0xc3, 0xb1, 0xc3, 0xa5, 0xc4,
    0xbc, 0xc3, 0xae, 0xc5, 0xbe, 0xc3, 0xa5, 0xc5,
    0xa3, 0xc3, 0xae, 0xe1, 0xbb, 0x9d, 0xc3, 0xb1
  ]);
  print(string); // '???ér???????????????'
}

如果是 stream 字節流則可以在 Stream 的 transform() 函數上指定 UTF8.decoder :

var lines = inputStream
    .transform(UTF8.decoder)
    .transform(new LineSplitter());
try {
  await for (var line in lines) {
    print('Got ${line.length} characters from stream');
  }

使用 UTF8.encode() 把字符串編碼為 UTF8 字節 流:

import 'dart:convert' show UTF8;

main() {
  List<int> expected = [
    0xc3, 0x8e, 0xc3, 0xb1, 0xc5, 0xa3, 0xc3, 0xa9,
    0x72, 0xc3, 0xb1, 0xc3, 0xa5, 0xc5, 0xa3, 0xc3,
    0xae, 0xc3, 0xb6, 0xc3, 0xb1, 0xc3, 0xa5, 0xc4,
    0xbc, 0xc3, 0xae, 0xc5, 0xbe, 0xc3, 0xa5, 0xc5,
    0xa3, 0xc3, 0xae, 0xe1, 0xbb, 0x9d, 0xc3, 0xb1
  ];

  List<int> encoded = UTF8.encode('???ér???????????????');

  assert(() {
    if (encoded.length != expected.length) return false;
    for (int i = 0; i < encoded.length; i++) {
      if (encoded[i] != expected[i]) return false;
    }
    return true;
  });
}

Other functionality

dart:convert 來包含轉換 ASCII 和 ISO-8859-1(Latin1) 的轉換器。詳情請參考 dart:convert 庫的 API 文檔。

dart:mirrors - reflection

dart:mirrors 庫提供了基本的反射支持。 使用 mirror 來查詢程序的結構,也可以 在運行時動態的調用方法或者函數。 dart:mirrors 庫在命令行和 web 應用中均可使用。 導入 dart:mirrors 即可開始使用。

警告: 使用 dart:mirrors 可能會導致 dart2js 生成的 JavaScript 代碼 文件非常大!

目前的解決方式是在導入 dart:mirrors 之前添加一個 @MirrorsUsed 注解。 詳情請參考 MirrorsUsed API 文檔。由于 dart:mirrors 庫依然還在 開發中,所以這個解決方案以后很有可能發生變化。

Symbols

mirror 系統使用 Symbol 類對象 來表達定義的 Dart 標識符名字。 Symbols 在混淆后的代碼也可以 使用。

如果在寫代碼的時候,已經知道 symbol 的名字了,則可以使用 #符號名字 的方式直接使用。 直接使用的 symbol 對象是編譯時常量,多次定義引用的是同一個對象。 如果名字不知道,則可以通過 Symbol 構造函數來 創建:

import 'dart:mirrors';

// If the symbol name is known at compile time.
const className = #MyClass;

// If the symbol name is dynamically determined.
var userInput = askUserForNameOfFunction();
var functionName = new Symbol(userInput);

在混淆代碼的時候,編譯器可能使用更加簡短的名字來替代原來的符號(symbol)名字。 要獲取原來的 symbol 名字,使用 MirrorSystem.getName() 函數。該函數 在代碼混淆的情況下,也能返回正確的 symbol 名字。

import 'dart:mirrors';

const className = #MyClass;
assert('MyClass' == MirrorSystem.getName(className));

Introspection

使用 mirror 功能來檢查程序的結構。可以檢查 類、庫以及對象等。

下面的示例使用 Person 類:

class Person {
  String firstName;
  String lastName;
  int age;

  Person(this.firstName, this.lastName, this.age);

  String get fullName => '$firstName $lastName';

  void greet(String other) {
    print('Hello there, $other!');
  }
}

在開始使用之前,需要在一個類或者對象上調用 reflect 函數來獲取 到 mirror。

Class mirrors

在任何 Type 上面調用 reflect 函數來獲取 ClassMirror :

ClassMirror mirror = reflectClass(Person);

assert('Person' ==
    MirrorSystem.getName(mirror.simpleName));

也可以在實例上調用 runtimeType 獲取該對象的 Type。

var person = new Person('Bob', 'Smith', 33);
ClassMirror mirror = reflectClass(person.runtimeType);
assert('Person' ==
    MirrorSystem.getName(mirror.simpleName));

獲取到 ClassMirror 后,就可以查詢類的構造函數、成員變量、等信息。 下面是列出類的所有構造函數的示例:

showConstructors(ClassMirror mirror) {
  var constructors = mirror.declarations.values
      .where((m) => m is MethodMirror && m.isConstructor);

  constructors.forEach((m) {
    print('The constructor ${m.simpleName} has '
          '${m.parameters.length} parameters.');
  });
}

下面是列出類的成員變量的示例:

showFields(ClassMirror mirror) {
  var fields = mirror.declarations.values
      .where((m) => m is VariableMirror);

  fields.forEach((VariableMirror m) {
    var finalStatus = m.isFinal ? 'final' : 'not final';
    var privateStatus = m.isPrivate ?
        'private' : 'not private';
    var typeAnnotation = m.type.simpleName;

    print('The field ${m.simpleName} is $privateStatus ' +
          'and $finalStatus and is annotated as ' +
          '$typeAnnotation.');
  });
}

詳情請參考 ClassMirror 的 API 文檔。

Instance mirrors

在對象上調用 reflect 函數可以獲取到一個 InstanceMirror 對象。

var p = new Person('Bob', 'Smith', 42);
InstanceMirror mirror = reflect(p);

如果你已經有個 InstanceMirror 對象了,但是想知道該對象反射的目標對象,則需要 調用 reflectee。

var person = mirror.reflectee;
assert(identical(p, person));

Invocation

獲取到 InstanceMirror 后,就可以調用里面的函數、getter和setter了。 詳細信息請參考 API docs for InstanceMirror 的 API 文檔。

Invoke methods

使用 InstanceMirror 的 invoke() 函數來調用對象的函數。 第一個參數為要調用的函數名字,第二個參數為該函數的 一個位置參數列表。第三個參數為可選參數,用來指定命名 參數。

var p = new Person('Bob', 'Smith', 42);
InstanceMirror mirror = reflect(p);

mirror.invoke(#greet, ['Shailen']);

Invoke getters and setters

使用 InstanceMirror 的 getField() 和 setField() 函數來 查詢和設置對象的屬性。

var p = new Person('Bob', 'Smith', 42);
InstanceMirror mirror = reflect(p);

// Get the value of a property.
var fullName = mirror.getField(#fullName).reflectee;
assert(fullName == 'Bob Smith');

// Set the value of a property.
mirror.setField(#firstName, 'Mary');
assert(p.firstName == 'Mary');

More information

文章 Reflection in Dart with Mirrors 更詳細 的介紹了反射功能。另外也可以參考 dart:mirror, 特別是 MirrorsUsed, ClassMirror, 和 InstanceMirror 這些類的 API 文檔。

Summary 這頁介紹了 Dart 應用中最常用的 內置庫,并沒有介紹所有的內置庫。 下面是一些其他參考資料: dart:collection, dart:isolate, 和 dart:typed_data. 使用 pub 工具可以使用其他第三方庫。 例如 args, logging, polymer, 和 test 庫都是第三方庫,可以 使用 pub 工具來使用。


所屬標簽

無標簽

官方入門指南

Flutter官方發布的入門指導,包括了如何在不同的平臺(Windows, Mac, Linux)上搭建開發環境,以及一些入門級的指導,以便您從零開始進入Flutter的世界,同時,一些Flutter的框架API,也是您開發時必不可少的工具書。

從這里進入


25选5玩法中奖