目錄

Dart 語言核心函式庫一覽

本頁介紹如何使用 Dart 核心函式庫中的主要功能。這只是一個概覽,並不全面。當你需要有關類別的更多詳細資訊時,請參閱Dart API 參考

dart:core
內建型別,集合和其他核心功能。該庫會被自動匯入到所有的 Dart 程式。

dart:async
支援非同步程式設計,包括Future和Stream等類別。

dart:math
數學常數和函式,以及隨機數產生器。

dart:convert
用於在不同資料表示之間進行轉換的編碼器和解碼器,包括 JSON 和 UTF-8。

dart:html
用於基於瀏覽器應用的 DOM 和其他 API。

dart:io
伺服器和命令列應用程式的 I/O 操作,包括 Flutter 應用,伺服器端應用,以及命令列指令碼。

本章只是一個概述;只涵蓋了幾個 dart:* 庫,不包括第三方庫。

更多庫資訊可以在 Pub siteDart web developer library guide. 查詢。所有 dart:* 庫的 API 文件可以在 Dart API reference 查詢,如果使用的是 Flutter 可以在 Flutter API reference. 查詢。

dart:core - 數字,集合,字串等

dart:core 庫 (API reference) 提供了一個少量但是重要的內建功能集合。該庫會被自動匯入每個 Dart 程式。

控制檯列印

最上層 print() 方法接受一個引數任意物件)並輸出顯示這個物件的字串值(由 toString() 返回) 到控制檯。

print(anObject);
print('I drink $tea.');

有關基本字串和 toString() 的更多資訊,參考 Strings in the language tour.

數字

dart:core 函式庫定義了 num ,int 以及 double 類,這些類別擁有一定的工具方法來處理數字。

使用 int 和 double 的 parse() 方法將字串轉換為整型或雙浮點型物件:

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

或者使用 num 的 parse() 方法,該方法可能會建立一個整數型別,否則為浮點型物件:

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() 方法將整型或雙精度浮點型別轉換為字串型別。使用 toStringAsFixed(). 指定小數點右邊的位數,使用 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);

For more information, see the API documentation for int, double, and num. Also see the dart:math section.

字元和正則表示式

在 Dart 中一個字串是一個固定不變的 UTF-16 編碼單元序列。語言概覽中有更多關於 strings 的內容。使用正則表示式 (RegExp 物件) 可以在字串內搜尋和替換部分字串。

String 定義了例如 split()contains()startsWith()endsWith() 等方法。

在字串中搜索

可以在字串內查詢特定字串的位置,以及檢查字串是否以特定字串作為開頭或結尾。例如:

// 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);

從字串中提取資料

可以獲取字串中的單個字元,將其作為字串或者整數。確切地說,實際上獲取的是單獨的UTF-16編碼單元; 諸如高音譜號符號 (‘\u{1D11E}’) 之類別的高編號字元分別為兩個編碼單元。

你也可以獲取字串中的子字串或者將一個字串分割為子字串列表:

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

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

// 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 (final 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);

首字母大小寫轉換

可以輕鬆的對字串的首字母大小寫進行轉換:

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

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

Trimming 和空字串

使用 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('  '.isNotEmpty);

替換部分字串

字串是不可變的物件,也就是說字串可以建立但是不能被修改。如果仔細閱讀了 String API docs, 你會注意到,沒有一個方法實際的改變了字串的狀態。例如,方法 replaceAll() 返回一個新字串,並沒有改變原始字串:

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

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

建構一個字串

要以程式碼方式產生字串,可以使用 StringBuffer 。在呼叫 toString() 之前, StringBuffer 不會產生新字串物件。 writeAll() 的第二個引數為可選引數,用來指定分隔符,本例中使用空格作為分隔符。

var sb = 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.');

正則表示式

RegExp 類提供與 JavaScript 正則表示式相同的功能。使用正則表示式可以對字串進行高效搜尋和模式匹配。

// Here's a regular expression for one or more digits.
var numbers = 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');

You can work directly with the RegExp class, too. The Match class provides access to a regular expression match.

var numbers = 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 (final match in numbers.allMatches(someDigits)) {
  print(match.group(0)); // 15, then 20
}

更多資訊

有關完整的方法列表,請參考 String API docs。另請參考 StringBuffer, Pattern, RegExp,Match 的 API 文件。

集合

Dart 附帶了核心集合 API ,其中包括 list、set 和 map 類別。

Lists

如語言概覽中介紹,lists 可以透過字面量來建立和初始化。另外,也可以使用 List 的建構函式。 List 類還定義了若干方法,用於向列表新增或刪除專案。

// Create an empty list of strings.
var grains = <String>[];
assert(grains.isEmpty);

// Create a list using 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.isEmpty);

// You can also create a List using one of the constructors.
var vegetables = List.filled(99, 'broccoli');
assert(vegetables.every((v) => v == 'broccoli'));

使用 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);

使用 sort() 方法排序一個 list 。你可以提供一個排序函式用於比較兩個物件。比較函式在 小於 時返回 \ <0,相等 時返回 0,bigger 時返回 > 0 。下面範例中使用 compareTo() 函式,該函式在 Comparable 中定義,並被 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 = <String>[];

fruits.add('apples');
var fruit = fruits[0];
assert(fruit is String);
fruits.add(5); // Error: 'int' can't be assigned to 'String'

全部的方法介紹,請參考 List API docs

Sets

在 Dart 中,set 是一個無序的,元素唯一的集合。因為一個 set 是無序的,所以無法透過下標(位置)獲取 set 中的元素。

// Create an empty set of strings.
var ingredients = <String>{};

// Add new items to it.
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);

// You can also create sets using
// one of the constructors.
var atomicNumbers = Set.from([79, 22, 54]);

使用 contains()containsAll() 來檢查一個或多個元素是否在 set 中:

var ingredients = Set<String>();
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 中的公共元素組成的 set。

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

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

全部的方法介紹,請參考 Set API docs

Maps

map 是一個無序的 key-value (鍵值對)集合,就是大家熟知的 dictionary 或者 hash。 map 將 kay 與 value 關聯,以便於檢索。和 JavaScript 不同,Dart 物件不是 map。

宣告 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 = Map();

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

透過大括號語法可以為 map 新增,獲取,設定元素。使用 remove() 方法從 map 中移除鍵值對。

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(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 是否存在是不可靠的。

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

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

如果當且僅當該 key 不存在於 map 中,且要為這個 key 賦值,可使用 putIfAbsent() 方法。該方法需要一個方法返回這個 value。

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

全部的方法介紹,請參考 Map API docs

公共集合方法

List, Set, 和 Map 共享許多集合中的常用功能。其中一些常見功能由 Iterable 類定義,這些函式由 List 和 Set 實現。

使用 isEmptyisNotEmpty 方法可以檢查 list, set 或 map 物件中是否包含元素:

var coffees = <String>[];
var teas = ['green', 'black', 'chamomile', 'earl grey'];
assert(coffees.isEmpty);
assert(teas.isNotEmpty);

使用 forEach() 可以讓 list, set 或 map 物件中的每個元素都使用一個方法。

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.
});

Iterable 提供 map() 方法,這個方法將所有結果返回到一個物件中。

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

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

使用 map().toList()map().toSet() ,可以強制在每個專案上立即呼叫函式。

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

使用 Iterable 的 where() 方法可以獲取所有匹配條件的元素。使用 Iterable 的 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));

有關方法的完整列表,請參考 Iterable API docs, 以及 List, Set, and Map.

URIs

在使用 URI(可能你會稱它為 URLs)時,Uri 類 提供對字串的編解碼操作。這些函式用來處理 URI 特有的字元,例如 = 。 Uri 類還可以解析和處理 URI—host,port,scheme等元件。

編碼和解碼完整合法的URI

使用 encodeFull()decodeFull() 方法,對 URI 中除了特殊字元(例如 /:&#)以外的字元進行編解碼,這些方法非常適合編解碼完整合法的 URI,並保留 URI 中的特殊字元。

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

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

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

注意上面程式碼只編碼了 somemessage 之間的空格。

編碼和解碼 URI 元件

使用 encodeComponent()decodeComponent() 方法,對 URI 中具有特殊含義的所有字串字元,特殊字元包括(但不限於)/&,和 :

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

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

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

注意上面程式碼編碼了所有的字元。例如 / 被編碼為 %2F

解析 URI

使用 Uri 物件的欄位(例如 path),來獲取一個 Uri 物件或者 URI 字串的一部分。使用 parse() 靜態方法,可以使用字串建立 Uri 物件。

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

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

有關 URI 元件的更多內容,參考 Uri API docs

建構 URI

使用 Uri() 建構函式,可以將各元件部分建構成 URI 。

var uri = Uri(
    scheme: 'https',
    host: 'example.org',
    path: '/foo/bar',
    fragment: 'frag',
    queryParameters: {'lang': 'dart'});
assert(uri.toString() == 'https://example.org/foo/bar?lang=dart#frag');

If you don’t need to specify a fragment, to create a URI with a http or https scheme, you can instead use the Uri.http or Uri.https factory constructors:

var httpUri = Uri.http('example.org', '/foo/bar', {'lang': 'dart'});
var httpsUri = Uri.https('example.org', '/foo/bar', {'lang': 'dart'});

assert(httpUri.toString() == 'http://example.org/foo/bar?lang=dart');
assert(httpsUri.toString() == 'https://example.org/foo/bar?lang=dart');

日期和時間

DateTime 物件代表某個時刻,時區可以是 UTC 或者本地時區。

DateTime 物件可以透過若干建構函式和方法建立:

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

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

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

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

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

// Parse an ISO 8601 date in the UTC time zone.
y2k = DateTime.parse('2000-01-01T00:00:00Z');

// Create a new DateTime from an existing one, adjusting just some properties:
var sameTimeLastYear = now.copyWith(year: now.year - 1);

日期中 millisecondsSinceEpoch 屬性返回自 “Unix 紀元(January 1, 1970, UTC)”以來的毫秒數:

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

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

Use the Duration class to calculate the difference between two dates and to shift a date forward or backward:

var y2k = 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.

參考 DateTimeDuration API 文件瞭解全部方法列表。

工具類

核心函式庫包含各種工具類,可用於排序,對映值以及迭代。

比較物件

如果實現了 Comparable 介面,也就是說可以將該物件與另一個物件進行比較,通常用於排序。 compareTo() 方法在 小於 時返回 < 0,在 相等 時返回 0,在 大於 時返回 > 0。

class Line implements Comparable<Line> {
  final int length;
  const Line(this.length);

  @override
  int compareTo(Line other) => length - other.length;
}

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

Implementing map keys

在 Dart 中每個物件會預設提供一個整數的雜湊值,因此在 map 中可以作為 key 來使用,重寫 hashCode 的 getter 方法來產生自訂雜湊值。如果重寫 hashCode 的 getter 方法,那麼可能還需要重寫 == 運算子。相等的(透過 == )物件必須擁有相同的雜湊值。雜湊值並不要求是唯一的,但是應該具有良好的分佈形態。

class Person {
  final String firstName, lastName;

  Person(this.firstName, this.lastName);

  // Override hashCode using the static hashing methods
  // provided by the `Object` class.
  @override
  int get hashCode => Object.hash(firstName, lastName);

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

void main() {
  var p1 = Person('Bob', 'Smith');
  var p2 = Person('Bob', 'Smith');
  var p3 = 'not a person';
  assert(p1.hashCode == p2.hashCode);
  assert(p1 == p2);
  assert(p1 != p3);
}

迭代

IterableIterator 類支援 for-in 迴圈。當建立一個類別的時候,繼承或者實現 Iterable,可以為該類提供用於 for-in 迴圈的 Iterators。實現 Iterator 來定義實際的遍歷操作。

如果你在 for-in 迴圈裡要建立一個可以提供 Iterator 的類,如果可以,請選擇 extend 或者 implement Iterable 的方式。 Implement Iterator 來定義一個實際的迭代能力。

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

class ProcessIterator implements Iterator<Process> {
  @override
  Process get current => ...
  @override
  bool moveNext() => ...
}

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

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

例外

Dart 核心函式庫定義了很多公共的例外和錯誤類別。例外通常是一些可以預見和預知的情況。錯誤是無法預見或者預防的情況。

兩個最常見的錯誤:

NoSuchMethodError

當方法的接受物件(可能為null)沒有實現該方法時丟擲。

ArgumentError

當方法在接受到一個不合法引數時丟擲。

通常透過丟擲一個應用特定的例外,來表示應用發生了錯誤。透過實現 Exception 介面來自訂例外:

class FooException implements Exception {
  final String? msg;

  const FooException([this.msg]);

  @override
  String toString() => msg ?? 'FooException';
}

更多內容,參考 Exceptions 以及 Exception API 文件。

弱參考和終結器 (finalizers)

Dart 語言支援 垃圾回收 (GC),即所有未被參考的 Dart 物件最終都會被垃圾回收並銷燬。某些涉及到原生資源和目標物件無法修改的場景, GC 的行為可能不會符合預期。

WeakReference 會儲存目標物件的參考,並且不會影響目標物件被 GC。另一種方案是使用 Expando 對物件新增一些屬性。

終結器 (Finalizer) 可以在物件已不再被參考時執行一個回呼(Callback)函式。然而,終結器的回呼(Callback)並不保證一定會執行。

NativeFinalizer 為使用 dart:ffi 與原生互動的程式碼提供了更加強力的回呼(Callback)保證。它的回呼(Callback)會在物件不再參考後至少呼叫一次。同時,它也可以用來關閉原生資源,例如資料庫連結和開啟的檔案。

想要確保一個物件不會過早地被回收,其對應的類可以實現 Finalizable 介面。當一個方法內的變數是 Finalizable,直到程式碼執行完畢後它才會被回收。

dart:async - 非同步程式設計

非同步程式設計通常使用回呼(Callback)方法來實現,但是 Dart 提供了其他方案:FutureStream 物件。 Future 類似與 JavaScript 中的 Promise ,代表在將來某個時刻會返回一個結果。 Stream 類可以用來獲取一系列的值,比如,一系列事件。 Future, Stream,以及更多內容,參考 dart:async library (API reference)。

dart:async 庫可以工作在 web 應用及 command-line 應用。透過 import dart:async 來使用。

import 'dart:async';

Future

在 Dart 庫中隨處可見 Future 物件,通常非同步函式返回的物件就是一個 Future。當一個 future 完成執行後,future 中的值就已經可以使用了。

使用 await

在直接使用 Future API 前,首先應該考慮 await 來替代。程式碼中使用 await 表示式會比直接使用 Future API 更容易理解。

閱讀思考下面程式碼。程式碼使用 Future 的 then() 方法在同一行執行了三個非同步函式,要等待上一個執行完成,再執行下一個任務。

void runUsingFuture() {
  // ...
  findEntryPoint().then((entryPoint) {
    return runExecutable(entryPoint, args);
  }).then(flushThenExit);
}

透過 await 表示式實現等價的程式碼,看起來非常像同步程式碼:

Future<void> runUsingAsyncAwait() async {
  // ...
  var entryPoint = await findEntryPoint();
  var exitCode = await runExecutable(entryPoint, args);
  await flushThenExit(exitCode);
}

async 函式能夠捕獲來自 Future 的例外。例如:

var entryPoint = await findEntryPoint();
try {
  var exitCode = await runExecutable(entryPoint, args);
  await flushThenExit(exitCode);
} catch (e) {
  // Handle the error...
}

更多關於 await 的使用及相關的 Dart 語言特徵,參考 Asynchrony support

基本用法

當 future 執行完成後,then() 中的程式碼會被執行。 then() 中的程式碼會在 future 完成後被執行。例如,HttpRequest.getString() 返回一個 future 物件,因為 HTTP 請求可能需要一段時間。當 Future 完成並且保證字串值有效後,使用 then() 來執行你需要的程式碼:

HttpRequest.getString(url).then((String result) {
  print(result);
});

使用 catchError() 來處理一些 Future 物件可能丟擲的錯誤或者例外。

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

then().catchError() 組合是 try-catch 的非同步版本。

鏈式非同步程式設計

then() 方法返回一個 Future 物件,這樣就提供了一個非常好的方式讓多個非同步方法按順序依次執行。如果用 then() 註冊的回呼(Callback)返回一個 Future ,那麼 then() 返回一個等價的 Future 。如果回呼(Callback)返回任何其他型別的值,那麼 then() 會建立一個以該值完成的新 Future 。

Future result = costlyQuery(url);
result
    .then((value) => expensiveWork(value))
    .then((_) => lengthyComputation())
    .then((_) => print('Done!'))
    .catchError((exception) {
  /* Handle exception... */
});

在上面的範例中,方法按下面順序執行:

  1. costlyQuery()
  2. expensiveWork()
  3. lengthyComputation()

這是使用 await 編寫的等效程式碼:

try {
  final value = await costlyQuery(url);
  await expensiveWork(value);
  await lengthyComputation();
  print('Done!');
} catch (e) {
  /* Handle exception... */
}

等待多個 Future

有時程式碼邏輯需要呼叫多個非同步函式,並等待它們全部完成後再繼續執行。使用 Future.wait() 靜態方法管理多個 Future 以及等待它們完成:

Future<void> deleteLotsOfFiles() async =>  ...
Future<void> copyLotsOfFiles() async =>  ...
Future<void> checksumLotsOfOtherFiles() async =>  ...

await Future.wait([
  deleteLotsOfFiles(),
  copyLotsOfFiles(),
  checksumLotsOfOtherFiles(),
]);
print('Done with all the long steps!');

Future.wait() returns a future which completes once all the provided futures have completed. It completes either with their results, or with an error if any of the provided futures fail.

Handling errors for multiple futures

You can also wait for parallel operations on an iterable or record of futures.

These extensions return a future with the resulting values of all provided futures. Unlike Future.wait, they also let you handle errors.

If any future in the collection completes with an error, wait completes with a ParallelWaitError. This allows the caller to handle individual errors and dispose successful results if necessary.

When you don’t need the result values from each individual future, use wait on an iterable of futures:

void main() async {
  Future<void> delete() async =>  ...
  Future<void> copy() async =>  ...
  Future<void> errorResult() async =>  ...
  
  try {
    // Wait for each future in a list, returns a list of futures:
    var results = await [delete(), copy(), errorResult()].wait;

    } on ParallelWaitError<List<bool?>, List<AsyncError?>> catch (e) {

    print(e.values[0]);    // Prints successful future
    print(e.values[1]);    // Prints successful future
    print(e.values[2]);    // Prints null when the result is an error

    print(e.errors[0]);    // Prints null when the result is successful
    print(e.errors[1]);    // Prints null when the result is successful
    print(e.errors[2]);    // Prints error
  }

}

When you do need the individual result values from each future, use wait on a record of futures. This provides the additional benefit that the futures can be of different types:

void main() async {
  Future<int> delete() async =>  ...
  Future<String> copy() async =>  ...
  Future<bool> errorResult() async =>  ...

  try {    
    // Wait for each future in a record, returns a record of futures:
    (int, String, bool) result = await (delete(), copy(), errorResult()).wait;
  
  } on ParallelWaitError<(int?, String?, bool?),
      (AsyncError?, AsyncError?, AsyncError?)> catch (e) {
    // ...
    }

  // Do something with the results:
  var deleteInt  = result.$1;
  var copyString = result.$2;
  var errorBool  = result.$3;
}

Stream

在 Dart API 中 Stream 物件隨處可見,Stream 用來表示一系列資料。例如,HTML 中的按鈕點選就是透過 stream 傳遞的。同樣也可以將檔案作為資料流來讀取。

非同步迴圈

有時,可以使用非同步 for迴圈 await for ,來替代 Stream API 。

思考下面範例函式。它使用 Stream 的 listen() 方法來訂閱檔案列表,傳入一個搜尋檔案或目錄的函式

void main(List<String> arguments) {
  // ...
  FileSystemEntity.isDirectory(searchPath).then((isDir) {
    if (isDir) {
      final startingDir = Directory(searchPath);
      startingDir.list().listen((entity) {
        if (entity is File) {
          searchFile(entity, searchTerms);
        }
      });
    } else {
      searchFile(File(searchPath), searchTerms);
    }
  });
}

下面是使用 await 表示式和非同步 for迴圈 (await for) 實現的等價的程式碼,看起來更像是同步程式碼:

void main(List<String> arguments) async {
  // ...
  if (await FileSystemEntity.isDirectory(searchPath)) {
    final startingDir = Directory(searchPath);
    await for (final entity in startingDir.list()) {
      if (entity is File) {
        searchFile(entity, searchTerms);
      }
    }
  } else {
    searchFile(File(searchPath), searchTerms);
  }
}

有關 await 的使用及 Dart 語言的相關資訊,參考 Asynchrony support

監聽流資料 (stream data)

使用 await for 或者使用 listen() 方法監聽 stream,來獲取每個到達的資料流值:

// Add an event handler to a button.
submitButton.onClick.listen((e) {
  // When the button is clicked, it runs this code.
  submitData();
});

下面範例中,ID 為 “submitInfo” button 提供的 onClick 屬性是一個 Stream 物件。

如果只關心其中一個事件,可以使用,例如,firstlast,或 single 屬性來獲取。要在處理時間前對事件進行測試,可以使用,例如 firstWhere()lastWhere(),或 singleWhere() 方法。

如果只關心事件中的一個子集,可以使用,例如,skip()skipWhile()take()takeWhile(),和 where()

傳遞流資料 (stream data)

常常,在使用流資料前需要改變資料的格式。使用 transform() 方法產生具有不同型別資料的流:

var lines =
    inputStream.transform(utf8.decoder).transform(const LineSplitter());

上面例子中使用了兩個 transformer 。第一個使用 utf8.decoder 將整型流轉換為字串流。接著,使用了 LineSplitter 將字串流轉換為多行字串流。這些 transformer 來自 dart:convert 庫(參考dart:convert section)。

處理錯誤和完成

處理錯誤和完成程式碼方式,取決於使用的是非同步 for迴圈(await for)還是 Stream API 。

如果使用的是非同步 for迴圈,那麼透過 try-catch 來處理錯誤。程式碼位於非同步 for迴圈之後,會在 stream 被關閉後執行。

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

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

如果使用的是 Stream API,那麼透過註冊 onError 監聽來處理錯誤。程式碼位於註冊的 onDone 中,會在 stream 被關閉後執行。

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

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

更多內容

更多在 command-line 應用中使用 Future 和 Stream 的例項,參考 dart:io 概覽 也可以參考下列文章和課程:

dart:math - 數學和隨機數

dart:math 庫(API reference)提供通用的功能,例如,正弦和餘弦,最大值和最小值,以及數學常數,例如 pie。大多數在 Math 庫中的功能是作為最上層函式實現的。

透過匯入 dart:math 來引入使用該函式庫。

import 'dart:math';

三角函式

Math 庫提供基本的三角函式:

// Cosine
assert(cos(pi) == -1.0);

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

最大值和最小值

Math 庫提供 max()min() 方法:

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

數學常數

在 Math 庫中可以找到你需要的數學常數,例如,pie 等等:

// See the Math library for additional constants.
print(e); // 2.718281828459045
print(pi); // 3.141592653589793
print(sqrt2); // 1.4142135623730951

隨機數

使用 Random 類產生隨機數。可以為 Random 建構函式提供一個可選的種子引數。

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

也可以產生隨機布林值序列:

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

更多內容

完整方法列表參考 Math API docs。在 API 文件中參考 num, int,double

dart:convert - 編解碼JSON,UTF-8等

dart:convert 庫(API reference)提供 JSON 和 UTF-8 轉換器,以及建立其他轉換器。 JSON 是一種用於表示結構化物件和集合的簡單文字格式。 UTF-8 是一種常見的可變寬度編碼,可以表示Unicode字集中的每個字元。

使用時,透過 import dart:convert 引入。

import 'dart:convert';

編解碼JSON

使用 jsonDecode() 解碼 JSON 編碼的字串為 Dart 物件:

// 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 = jsonDecode(jsonString);
assert(scores is List);

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

使用 jsonEncode() 編碼 Dart 物件為 JSON 格式的字串:

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

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

只有 int, double, String, bool, null, List, 或者 Map 型別物件可以直接編碼成 JSON。 List 和 Map 物件進行遞迴編碼。

不能直接編碼的物件有兩種方式對其編碼。第一種方式是呼叫 jsonEncode() 時賦值第二個引數,這個引數是一個函式,該函式返回一個能夠直接編碼的物件第二種方式是省略第二個引數,著這種情況下編碼器呼叫物件的 toJson() 方法。

更多範例及 JSON 包相關連結,參考 JSON Support

編解碼 UTF-8 字元

使用 utf8.decode() 解碼 UTF8 編碼的字元創為 Dart 字串:

List<int> utf8Bytes = [
  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
];

var funnyWord = utf8.decode(utf8Bytes);

assert(funnyWord == 'Îñţérñåţîöñåļîžåţîờñ');

將 UTF-8 字串流轉換為 Dart 字串,為 Stream 的 transform() 方法上指定 utf8.decoder

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

使用 utf8.encode() 將 Dart 字串編碼為一個 UTF8 編碼的位元組流:

List<int> encoded = utf8.encode('Îñţérñåţîöñåļîžåţîờñ');

assert(encoded.length == utf8Bytes.length);
for (int i = 0; i < encoded.length; i++) {
  assert(encoded[i] == utf8Bytes[i]);
}

其他功能

dart:convert 庫同樣包含 ASCII 和 ISO-8859-1 (Latin1) 轉換器。更多詳情,參考 API docs for the dart:convert library。

dart:html - 基於瀏覽器應用

Use the dart:html library to program the browser, manipulate objects and elements in the DOM, and access HTML5 APIs. DOM stands for Document Object Model, which describes the hierarchy of an HTML page.

Other common uses of dart:html are manipulating styles (CSS), getting data using HTTP requests, and exchanging data using WebSockets. HTML5 (and dart:html) has many additional APIs that this section doesn’t cover. Only web apps can use dart:html, not command-line apps.

To use the HTML library in your web app, import dart:html:

import 'dart:html';

Manipulating the DOM

To use the DOM, you need to know about windows, documents, elements, and nodes.

A Window object represents the actual window of the web browser. Each Window has a Document object, which points to the document that’s currently loaded. The Window object also has accessors to various APIs such as IndexedDB (for storing data), requestAnimationFrame (for animations), and more. In tabbed browsers, each tab has its own Window object.

With the Document object, you can create and manipulate Element objects within the document. Note that the document itself is an element and can be manipulated.

The DOM models a tree of Nodes. These nodes are often elements, but they can also be attributes, text, comments, and other DOM types. Except for the root node, which has no parent, each node in the DOM has one parent and might have many children.

Finding elements

To manipulate an element, you first need an object that represents it. You can get this object using a query.

Find one or more elements using the top-level functions querySelector() and querySelectorAll(). You can query by ID, class, tag, name, or any combination of these. The CSS Selector Specification guide defines the formats of the selectors such as using a # prefix to specify IDs and a period (.) for classes.

The querySelector() function returns the first element that matches the selector, while querySelectorAll()returns a collection of elements that match the selector.

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

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

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

// Find all text inputs.
List<Element> textInputElements = 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> specialParagraphElements = querySelectorAll('#id p.class');

Manipulating elements

You can use properties to change the state of an element. Node and its subtype Element define the properties that all elements have. For example, all elements have classes, hidden, id, style, and title properties that you can use to set state. Subclasses of Element define additional properties, such as the href property of AnchorElement.

Consider this example of specifying an anchor element in HTML:

<a id="example" href="/another/example">link text</a>

This <a> tag specifies an element with an href attribute and a text node (accessible via a text property) that contains the string “link text”. To change the URL that the link goes to, you can use AnchorElement’s href property:

var anchor = querySelector('#example') as AnchorElement;
anchor.href = 'https://dart.dev';

Often you need to set properties on multiple elements. For example, the following code sets the hidden property of all elements that have a class of “mac”, “win”, or “linux”. Setting the hidden property to true has the same effect as adding display: none to the CSS.

<!-- 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:
const osList = ['macos', 'windows', 'linux'];
final userOs = determineUserOs();

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

  // 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 (final elem in querySelectorAll('.$os')) {
    elem.hidden = !shouldShow; // Show or hide.
  }
}

When the right property isn’t available or convenient, you can use Element’s attributes property. This property is a Map<String, String>, where the keys are attribute names. For a list of attribute names and their meanings, see the MDN Attributes page. Here’s an example of setting an attribute’s value:

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

Creating elements

You can add to existing HTML pages by creating new elements and attaching them to the DOM. Here’s an example of creating a paragraph (<p>) element:

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

You can also create an element by parsing HTML text. Any child elements are also parsed and created.

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

Note that elem2 is a ParagraphElement in the preceding example.

Attach the newly created element to the document by assigning a parent to the element. You can add an element to any existing element’s children. In the following example, body is an element, and its child elements are accessible (as a List<Element>) from the children property.

document.body!.children.add(elem2);

Adding, replacing, and removing nodes

Recall that elements are just a kind of node. You can find all the children of a node using the nodes property of Node, which returns a List<Node> (as opposed to children, which omits non-Element nodes). Once you have this list, you can use the usual List methods and operators to manipulate the children of the node.

To add a node as the last child of its parent, use the List add() method:

querySelector('#inputs')!.nodes.add(elem);

To replace a node, use the Node replaceWith() method:

querySelector('#status')!.replaceWith(elem);

To remove a node, use the Node remove() method:

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

Manipulating CSS styles

CSS, or cascading style sheets, defines the presentation styles of DOM elements. You can change the appearance of an element by attaching ID and class attributes to it.

Each element has a classes field, which is a list. Add and remove CSS classes simply by adding and removing strings from this collection. For example, the following sample adds the warning class to an element:

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

It’s often very efficient to find an element by ID. You can dynamically set an element ID with the id property:

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

You can reduce the redundant text in this example by using method cascades:

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

While using IDs and classes to associate an element with a set of styles is best practice, sometimes you want to attach a specific style directly to the element:

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

Handling events

To respond to external events such as clicks, changes of focus, and selections, add an event listener. You can add an event listener to any element on the page. Event dispatch and propagation is a complicated subject; research the details if you’re new to web programming.

Add an event handler using element.onEvent.listen(function), where Event is the event name and function is the event handler.

For example, here’s how you can handle clicks on a button:

// 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();
});

Events can propagate up and down through the DOM tree. To discover which element originally fired the event, use e.target:

document.body!.onClick.listen((e) {
  final clickedElem = e.target;
  // ...
});

To see all the events for which you can register an event listener, look for “onEventType” properties in the API docs for Element and its subclasses. Some common events include:

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

Using HTTP resources with HttpRequest

Formerly known as XMLHttpRequest, the HttpRequest class gives you access to HTTP resources from within your browser-based app. Traditionally, AJAX-style apps make heavy use of HttpRequest. Use HttpRequest to dynamically load JSON data or any other resource from a web server. You can also dynamically send data to a web server.

Getting data from the server

The HttpRequest static method getString() is an easy way to get data from a web server. Use await with the getString() call to ensure that you have the data before continuing execution.

void main() async {
  String pageHtml = await HttpRequest.getString(url);
  // Do something with pageHtml...
}

Use try-catch to specify an error handler:

try {
  var data = await HttpRequest.getString(jsonUri);
  // Process data...
} catch (e) {
  // Handle exception...
}

If you need access to the HttpRequest, not just the text data it retrieves, you can use the request() static method instead of getString(). Here’s an example of reading XML data:

void main() async {
  HttpRequest req = await HttpRequest.request(
    url,
    method: 'HEAD',
  );
  if (req.status == 200) {
    // Successful URL access...
  }
  // ···
}

You can also use the full API to handle more interesting cases. For example, you can set arbitrary headers.

The general flow for using the full API of HttpRequest is as follows:

  1. Create the HttpRequest object.
  2. Open the URL with either GET or POST.
  3. Attach event handlers.
  4. Send the request.

For example:

var request = HttpRequest();
request
  ..open('POST', url)
  ..onLoadEnd.listen((e) => requestComplete(request))
  ..send(encodedData);

Sending data to the server

HttpRequest can send data to the server using the HTTP method POST. For example, you might want to dynamically submit data to a form handler. Sending JSON data to a RESTful web service is another common example.

Submitting data to a form handler requires you to provide name-value pairs as URI-encoded strings. (Information about the URI class is in the URIs section of the Dart Library Tour.) You must also set the Content-type header to application/x-www-form-urlencoded if you wish to send data to a form handler.

String encodeMap(Map<String, String> data) => data.entries
    .map((e) =>
        '${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value)}')
    .join('&');

void main() async {
  const data = {'dart': 'fun', 'angular': 'productive'};

  var request = HttpRequest();
  request
    ..open('POST', url)
    ..setRequestHeader(
      'Content-type',
      'application/x-www-form-urlencoded',
    )
    ..send(encodeMap(data));

  await request.onLoadEnd.first;

  if (request.status == 200) {
    // Successful URL access...
  }
  // ···
}

Sending and receiving real-time data with WebSockets

A WebSocket allows your web app to exchange data with a server interactively—no polling necessary. A server creates the WebSocket and listens for requests on a URL that starts with ws://—for example, ws://127.0.0.1:1337/ws. The data transmitted over a WebSocket can be a string or a blob. Often, the data is a JSON-formatted string.

To use a WebSocket in your web app, first create a WebSocket object, passing the WebSocket URL as an argument:

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

Sending data

To send string data on the WebSocket, use the send() method:

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

Receiving data

To receive data on the WebSocket, register a listener for message events:

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

The message event handler receives a MessageEvent object. This object’s data field has the data from the server.

Handling WebSocket events

Your app can handle the following WebSocket events: open, close, error, and (as shown earlier) message. Here’s an example of a method that creates a WebSocket object and registers handlers for open, close, error, and message events:

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

  print('Connecting to websocket');

  void scheduleReconnect() {
    if (!reconnectScheduled) {
      Timer(Duration(seconds: 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

This section barely scratched the surface of using the dart:html library. For more information, see the documentation for dart:html. Dart has additional libraries for more specialized web APIs, such as web audio, IndexedDB, and WebGL.

For more information about Dart web libraries, see the web library overview.

dart:io - 伺服器和命令列應用程式的 I/O 。

The dart:io library provides APIs to deal with files, directories, processes, sockets, WebSockets, and HTTP clients and servers.

In general, the dart:io library implements and promotes an asynchronous API. Synchronous methods can easily block an application, making it difficult to scale. Therefore, most operations return results via Future or Stream objects, a pattern common with modern server platforms such as Node.js.

The few synchronous methods in the dart:io library are clearly marked with a Sync suffix on the method name. Synchronous methods aren’t covered here.

To use the dart:io library you must import it:

import 'dart:io';

Files and directories

The I/O library enables command-line apps to read and write files and browse directories. You have two choices for reading the contents of a file: all at once, or streaming. Reading a file all at once requires enough memory to store all the contents of the file. If the file is very large or you want to process it while reading it, you should use a Stream, as described in Streaming file contents.

Reading a file as text

When reading a text file encoded using UTF-8, you can read the entire file contents with readAsString(). When the individual lines are important, you can use readAsLines(). In both cases, a Future object is returned that provides the contents of the file as one or more strings.

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

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

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

Reading a file as binary

The following code reads an entire file as bytes into a list of ints. The call to readAsBytes() returns a Future, which provides the result when it’s available.

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

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

Handling errors

To capture errors so they don’t result in uncaught exceptions, you can register a catchError handler on the Future, or (in an async function) use try-catch:

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

Streaming file contents

Use a Stream to read a file, a little at a time. You can use either the Stream API or await for, part of Dart’s asynchrony support.

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

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

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

Writing file contents

You can use an IOSink to write data to a file. Use the File openWrite() method to get an IOSink that you can write to. The default mode, FileMode.write, completely overwrites existing data in the file.

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

To add to the end of the file, use the optional mode parameter to specify FileMode.append:

var sink = logFile.openWrite(mode: FileMode.append);

To write binary data, use add(List<int> data).

Listing files in a directory

Finding all files and subdirectories for a directory is an asynchronous operation. The list() method returns a Stream that emits an object when a file or directory is encountered.

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

  try {
    var dirList = dir.list();
    await for (final 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

The File and Directory classes contain other functionality, including but not limited to:

  • Creating a file or directory: create() in File and Directory
  • Deleting a file or directory: delete() in File and Directory
  • Getting the length of a file: length() in File
  • Getting random access to a file: open() in File

Refer to the API docs for File and Directory for a full list of methods.

HTTP clients and servers

The dart:io library provides classes that command-line apps can use for accessing HTTP resources, as well as running HTTP servers.

HTTP server

The HttpServer class provides the low-level functionality for building web servers. You can match request handlers, set headers, stream data, and more.

The following sample web server returns simple text information. This server listens on port 8888 and address 127.0.0.1 (localhost), responding to requests for the path /dart. For any other path, the response is status code 404 (page not found).

void main() async {
  final requests = await HttpServer.bind('localhost', 8888);
  await for (final request in requests) {
    processRequest(request);
  }
}

void processRequest(HttpRequest request) {
  print('Got request for ${request.uri.path}');
  final response = request.response;
  if (request.uri.path == '/dart') {
    response
      ..headers.contentType = ContentType(
        'text',
        'plain',
      )
      ..write('Hello from the server');
  } else {
    response.statusCode = HttpStatus.notFound;
  }
  response.close();
}

HTTP client

The HttpClient class helps you connect to HTTP resources from your Dart command-line or server-side application. You can set headers, use HTTP methods, and read and write data. The HttpClient class does not work in browser-based apps. When programming in the browser, use the dart:html HttpRequest class. Here’s an example of using HttpClient:

void main() async {
  var url = Uri.parse('http://localhost:8888/dart');
  var httpClient = HttpClient();
  var request = await httpClient.getUrl(url);
  var response = await request.close();
  var data = await utf8.decoder.bind(response).toList();
  print('Response ${response.statusCode}: $data');
  httpClient.close();
}

More information

This page showed how to use the major features of the dart:io library. Besides the APIs discussed in this section, the dart:io library also provides APIs for processes, sockets, and web sockets. For more information about server-side and command-line app development, see the server-side Dart overview.

總結

本頁向您介紹了 Dart 內建庫中最常用的功能。但是,並沒有涵蓋所有內建庫。您可能想要檢視的其他內容包括 dart:collectiondart:typed_data, ,以及特定於平台的函式庫,如 Dart web development librariesFlutter libraries.

您可以使用 pub 包管理 工具獲得更多庫。 collection, crypto, http, intl, 以及 test 以上只是簡單的列舉了一些可以透過 pub 安裝的函式庫。

要了解有關 Dart 語言的更多資訊,請參考 語言概覽