Dart语言内置库介绍

Posted MobileDev小园地

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dart语言内置库介绍相关的知识,希望对你有一定的参考价值。

这篇文档介绍了如何使用下列库的一些核心功能,这些库在所有的Dart平台上都支持。


dart:core

包括内建类型、集合和一些其他核心功能。这个库会在每个dart程序里自动导入。

dart:async

支持异步编程。Future和Stream类就在这个库里。

dart:math

包括数学常量、函数,还有一个随机数生成器。

dart:convert

不同数据格式(包括JSON和UTF-8)之间转换的编码器和解码器。


这篇文章只是一个简介,它只介绍了一小部分的dart:*库,不包含第三方库。另两个平台相关的库dart:io和dart:html会在其他文章里介绍。

其他可以获取库相关知识的地方是Pub Site还有Dart 网页开发库文档。当然,所有dart:*的API文档可以在Dart API规范里找到,或者如果在使用Flutter,在Flutter API规范里也能找到。


dart:core - 数字、集合、字符串等


库dart:core提供了一个小的但是非常重要的内置功能集合。这个库会在每个程序里被自动导入。


在控制台里打印

全局的print()方法只需要一个参数(任何对象),它会在控制台上展示这个对象的字符串值(在toString()返回)。

print(anyObject);
print('I drink $tea');


数字

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方法,它可以根据情况将一个字符串转为int或者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()方法可以将一个int或者double转为字符串。要指定小数点后的位数,使用toStringAsFixed(),要指定一个字符串里的有效数字位数,使用toStringAsPrecision():

// 把int转为字符串
assert(42.toString() == '42');

// 把double转为字符串
assert(123.456.toString() == '123.456');

// 指定小数点后的位数
assert(123.456.toStringAsFixed(2) == '123.45');

// 指定有效字符的位数
assert(123.456.toStringAsPrecision(2) == '1.2e+2');
assert(double.parse('1.2e+2') == 120.0);


字符串和正则表达式

Dart语言里的字符串是一个由不可变的UTF-16字符单位组成的序列。之前的Dart语言入门系列文章里有很多介绍字符串的内容。另外,正则表达式(RegExp对象)可以用来在字符串里查找或者替换。

String类定义了一些常用方法如split(),contains(),startsWith(),endsWith()等。


在字符串里进行搜索

你可以在字符串里找到特定的位置,同时也可以检测一个字符串是否是以某种特定的pattern开始或者结束。例如:

// 检测一个字符串是否包含另一个字符串
assert('Never odd or even'.contains('odd'));

// 检测一个字符串是否以另一个字符串开始
assert('Never odd or even'.startsWith('Never'));

// 检测一个字符串是否以另一个字符串结束
assert('Never odd or even'.endsWith('even'));

// 在一个字符串里查找一个字符串的位置
assert('Never odd or even'.indexOf('odd') == 6);


从字符串里获取数据

字符串里可以获取到独立的字符,作为String和int类型。准确来讲,你可以获取到独立的UTF-16字节码单位;编码比较大的字符就像高音谱号字符('u{1D11E}')由两个字节码单位组成。

你也可以从字符串里获取一个子字符串或者把字符串分割成子字符串的列表:

// 获取一个子字符串
assert('Never odd or even'.substring(69) == 'odd');

// 使用特定字符模式来分割一个字符串
var parts = 'structured web apps'.split(' ');
assert(parts.length == 3);
assert(parts[0] == 'structured');

// 通过索引获取一个字节码单位(作为一个字符串)
assert('Never odd or even'[0] == 'N');

// 使用一个以空字符串作为参数的split()函数
// 来获取字符串里所有字符的列表(每一个都是一个字符串)
// 非常方便用来迭代
for (var char in 'hello'.split('')) {
  print(var);
}

// 获取一个字符串中所有UTF-16字节码的列表
var codeUnitList =
    'Never odd or even'.codeUnits.toList();
assert(codeUnitList[0] == 78);


转化字符串为大写或者小写形式

字符串可以非常方便地转为大写形式或者小写形式:

// 转为大写形式
assert('structured web apps'.toUpperCase() ==
    'STRUCTURED WEB APPS');

// 转为小写形式
assert('STRUCTURED WEB APPS'.toLowerCase() ==
    'structured web apps');


注意,这些函数并不是对每一种语言都生效。例如土耳其语里没有点的i就不能正确转换。


字符串前后去空和空字符串

使用trim()方法来去掉字符串首尾的所有空字符。使用isEmpty属性来检测一个字符串是否为空字符串(长度为0)。

// 字符串首尾去空
assert(' hello '.trim() == 'hello');

// 检查一个字符串是否为空
assert(''.isEmpty);

// 只包含空格的字符串并不是空字符串
assert(' '.isNotEmpty);


替换字符串的部分内容

字符串是常量对象,意思是可以创建字符串,但是不能修改它们。如果仔细阅读String的接口文档,你会发现没有任何方法会实际上改变字符串的状态。例如,replaceAll()方法也不会改变原始字符串,它会返回一个全新的字符串:

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

// greetingTemplate并没有改变
assert(greeting != greetingTemplate);


构建字符串

要更有效率地生成一个字符串,可以使用StringBuffer,它在调用toString()之前并不会创建一个新的String对象。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正则表达式一样的能力。使用正则表达式来进行高效的字符串查找和模式匹配。

// 下面是一个或者多个数字的正则表达式
var numbers = RegExp(r'\d+');

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

// contains()方法可以使用正则表达式
assert(!allCharacters.contains(numbers));
assert(someDigits.contains(numbers));

// 把每一个匹配的地方都替换为另一个字符串
var exedOut = someDigits.replaceAll(numbers, 'XX');
assert(exedOut = 'llams live XX to XX years');


你也可以直接操作RegExp类。类Match提供了对一个正则表达式匹配的操作。

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

// 测试字符串中是否有正则表达式匹配
assert(numers.hasMatch(someDigits));

// 循环处理所有的匹配
for (var match in numbers.allMatches(someDigits)) {
  print(match.group(0)); // 分别打印15和20
}


集合

Dart支持一个核心的集合接口,包括lists,sets和maps。


Lists

在Dart语言入门简介中说过,字面量可以用来创建和初始化列表。List的构造函数也可以来创建列表。List类也定义了一些添加和删除元素的方法。

// 使用List构造函数
var vegetables = List();

// 简单地使用字面量
var fruits = ['apples''oranges'];

// 往List里添加元素
fruits.add('kiwis');

// 往列表中添加多个元素
fruits.addAll('grapes''bananas');

// 获取列表长度
assert(fruits.length = 5);

// 删除一个列表项
var appleIndex = fruits.indexOf('apples');
fruits.removeAt(appleIndex);
assert(fruits.length == 4);

// 删除列表里的所有元素
fruits.clear();
assert(fruits.length == 0);


使用index()方法来获取对象在列表中的索引:

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

// 使用索引访问一个列表项
assert(fruits[0] == 'apples');

// 在列表中获取一个元素
assert(fruits.indexOf('apples') == 0);


使用sort()方法来对列表进行排序。你可以提供一个用来进行排序的比较函数。这个排序函数必须对更小的值返回小于0的数字,对相等的值返回0,对更大的值返回大于0的数字。下面的例子使用Comparable类定义的compareTo()方法和它的字符串版本实现。

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

// 对列表进行排序
fruits.sort((a, b) => a.compareTo(b));
assert(fruits[0] == 'apples');


Lists是参数化的类型,可以指定列表中包含的元素类型。

// 这个列表只包含字符串
var fruits = List<String>();

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


Sets

Dart中Set是包含不重复元素的无序集合。因为Set是无序的,所以不能通过索引(或者位置)来访问一个Set的元素。

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

// 往Set里添加一个重复元素没有任何作用
ingredients.add('gold');
assert(ingredients.length == 3);

// 从Set里删除一个元素
ingredients.remove('gold');
assert(ingredients.length == 2);


使用contains()和containsAll()来检查一个或多个对象是否在Set里:

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

assert(ingredients.contains('titanium'));

assert(ingredients.containsAll(['titanium''xenon']));


取交集操作就是获得一个包含两个Set中共有元素的Set。

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

// 创建两个Set的交集
var nobleGases = Set.from(['xenon''argon']);
var intersection = ingredients.intersection(nobleGases);
assert(intersection.length == 1);
assert(intersection.contains('xenon'));


Maps

一个map,通常被称为字典或者散列,是一个由键值对组成的无序集合。Maps使用键关联值使得获取值非常方便。和JavaScript不同,Dart的对象并不是maps。

可以使用简洁的字面量语法来声明一个map,也可以使用传统的构造函数:

// Maps经常使用字符串作为键
var hawaiianBeaches = {
  'Oahu': ['Waikiki''Kailua''Waimanalo'],
  'Big Island': ['Wailea Bay''Polulu Beach'],
  'Kauai': ['Hanalei''Poipu']
};

// Maps可以用构造函数来创建
var searchTerms = Map();

// Maps是参数化的类型,可以指定键和值分别是什么类型的
var nobleGases = Map<intString>();


可以使用中括号语法来添加、获取和设置map的元素。使用remove()函数来从一个map中删除一个键和它的值。

var nobleGases = {54'xenon'};

// 通过键来获取值
assert(nobleGases[54] == 'xenon');

// 检查map中是否包含一个键
assert(nobleGases.containsKey(54));

// 删除一个键和它的值
nobleGases.remove(54);
assert(!nobleGases.containsKey(54));


也可以从map中获取所有键的集合或者所有值的集合。

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

// 获取所有键组成的无序集合(一个Iterable类型)
var keys = hawaiianBeaches.keys;

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

// 获取所有值组成的无序集合(一个列表的迭代类型)
var values = hawaiianBeaches.values;
assert(values.length == 3);
assert(values.any((v) => v.contains('Waikiki')));


要检查一个map中是否包含一个键,请使用containsKey()。因为map中的值可以是null,不能简单地通过获取这个键所对应的值,并检查这个值是否为null来判断一个键是不是存在。

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

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


要给一个map中不存在的键赋值时,使用putIfAbsent()方法,并且必须提供一个返回值的函数作为它的参数。

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


通用集合方法

List, Set和Map会共用一些在许多集合中发现的通用方法。一些通用方法声明在Iterable类中,List和Set实现了这些方法。

注意:尽管Map并没有实现Iterable,但可以通过Map的keys和values属性来获取可迭代对象。


使用isEmpty或者isNotEmpty属性来检查一个list, set或者map是否包含元素:

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


要为list, set或者map中的每一个元素都调用一个函数,可以使用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 $key and swim at $v');
})


Iterables提供了map()方法,会返回包含所有结果的一个对象

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

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


注意:map()函数返回的对象是一个Iterable,它是懒求值的:函数直到要从返回的对象里获取一个值时才会调用。


要强制函数在每个元素上立即执行,使用map().toList()或者map().toSet():

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


使用Iterable的where()方法来获取满足某一个条件的所有元素的集合。使用Iterable的any()或者every()方法来检查是否有一些或者全部的集合元素满足某一个条件。

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

bool isDecaffeinated(String teaName) =>
    teaName = 'chamomile';

// 使用where()来寻找使得上边的函数返回true的元素
var decaffeinatedTeas =
    teas.where((tea) => isDecaffeinated(tea));
// or teas.where(isDecaffeinated);

// 使用any()来检查是否至少有一个元素满足条件
assert(teas.any(isDecaffeinated));

// 使用every()来检查是否所有元素都满足条件
assert(!teas.every(isDecaffeinated));


URIs

Url类提供了对URIs(或者是URLs)中使用的字符串的编码和解码方法。这些方法处理URIs里的特殊字符,如&和=。Uri类也可以解析一个URI的各个组成部分:host,port,schema等。


标准格式编解码

使用encodeFull()和decodeFull()方法来对URIs里除了一些有特殊含义字符(如/、:、&;#)外其他字符进行编解码。这些方法非常方便来把一个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和where中间的空格被编码了。


URIs各组成部分的编解码

要对字符串中所有对URI有特殊意义的字符(包括但不限于/,&,:)进行编解码,使用encodeComponent()和decodeComponent()方法。

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。


解析URIs

如果有一个Uri的对象或者URI字符串,可以使用Uri的属性例如path来获取它的各个组成部分。要从一个字符串生成一个Uri,使用parse()静态方法。

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

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


创建URIs

可以使用Uri()构造函数通过各个独立的部分来构建一个URI:

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


日期和时间

DateTime对象代表着一个时间点。时区可以是UTC或者当地时间。

可以使用多种构造函数来创建一个DateTime对象:

// 获取当前的日期和时间
var now = DateTime.now();

// 在当前时区创建一个DateTime对象
var y2k = DateTime(2000); // 2000年1月1日

// 指定月份和日期
y2k = DateTime(200012); // 2000年1月2日

// 指定为UTC时间
y2k = DateTime.utc(2000); // 1/1/2000, UTC

// 通过距离Unix纪元的毫秒数来指定日期和时间
y2k = DateTime.fromMillisecondsSinceEpoch(946684800000,
    isUtc: true);

// 解析一个ISO 8601日期
y2k = DateTime.parse('2000-01-01T00:00:00Z');


日期的millisecondsSinceEpoch返回一个从"Unix纪元"(1970年1月1日,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)


使用Duration类来计算两个不同日期之间的差距,或者向前或者向后移动日期:

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

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

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

// 计算两个日期的差距,返回一个Duration对象
var duration = y2001.difference(y2k);
assert(duration.inDays == 366); // 2000年是闰年


注意:使用Duration来移动日期,有可能会出问题,比如冬夏令时时钟调整。如果必须要移动日期,使用UTC的日期。


实用工具类

Dart核心库包含各种不同的实用类,对排序、匹配值、迭代等非常有用。


对象比较

实现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);
}


实现map的键

每一个Dart中的对象都会自动生成一个整形的hash code(散列码),可以作为map中的键来使用。但是你可以覆写这个hash code的getter方法来返回一个自定义的值。如果要这么做的话,你同样可能要覆写==运算符。对象相等(通过==比较)意味着它们有相同的hash code值。这个hash code可以不是唯一的,但是要分布的比较好。

class Person {
  final String firstName, secondName;

  Person(this.firstName, this.secondName);

  // 使用effective java第十一章的方法来覆写hashCode
  @override
  int get hashCode {
    int result = 17;
    result = 37 * result + fristName.hashCode;
    result = 37 * result + secondName.hashCode;
    return result;
  }

  // 一般情况下,如果覆写了hashCode,同样要覆写==运算符
  @override
  bool operator ==(dynamic other) {
    if (other is! Person) return false;
    Person person = other;
    return (person.firstName == firstName &&
        person.secondName == 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);
}


迭代器

Iterable和Iteration类支持for-in循环。任何时候要创建一个可以生成在for-in循环里使用迭代器的类,继承(如果可能的话)或者实现Iterable接口。实现Iteration来定义一个真实的迭代能力。

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

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

// 一个虚构的类可以迭代所有的processes
// 继承自一个[Iterable]的子类
class Processes extends IterableBase<Process{
  @override
  final Iterator<Process> iterator = ProcessIterator();
}

void main() {
  // Iterable对象可以在for-in循环使用
  for (var 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';
}


dart:async - 异步编程


异步编程通常会使用回调函数,但是Dart采用另外的方式:Future和Stream对象。一个Future就像一个对一段时间后返回结果的承诺。一个Stream是获取一个值(如事件)序列的方式。Future, Stream还有其他的一些异步类定义在dart:async库中。


注意:你并不总是需要直接使用Future或者Stream的接口。Dart语言支持使用关键字如async和await来进行异步编码。参考Dart入门类文章的异步支持章节。


dart:async库既可以在web程序也可以在命令行程序使用。记得导入dart:async:

import 'dart:async';


版本提示:在Dart2.1中,不需要导入dart:async来使用Future和Stream的API,因为dart:core会导出这些类。


Future

Future对象在Dart库中的出现,是它经常作为异步函数返回的结果。当异步函数完成时,它的值就可以使用。


使用await

在直接使用Future的接口之前,考虑下能否使用await来替代。使用await的表达式比使用Future接口的代码更容易理解。

考虑下面的函数,它使用了Future的then()方法来在一行代码里执行3个异步函数,每一个异步函数(第一个除外)都等待它上一个异步函数的完成。

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


同样效果的代码使用await表达式,它更像是同步编程:

runUsingFuture() async {
  // ...
  var entryPoint = await entryfindEntryPoint();
  var exitCode = await runExecutable(entryPoint, args);
  await flushThenExit(exitCode);
}


一个异步函数可以从Futures里捕获异常。例如:

runUsingFuture() async {
  // ...
  var entryPoint = await entryfindEntryPoint();
  try {
    var exitCode = await runExecutable(entryPoint, args);
    await flushThenExit(exitCode);
  } catch (e) {
    // 处理错误。。。
  }
}


重要提示:异步函数返回Future对象。如果不想让你的函数返回Future,可以使用另外的方式。例如,你可以在另一个包装函数里调用一个异步函数。


基本用法

可以使用then()方法来安排代码在future完成时再执行。例如,HttpRequest.getString()会返回一个Future,因为Http请求是耗时的。使用then()可以在Future完成、承诺的字符串值可用时才调用一些代码:

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


使用catchError()来处理Future对象可能抛出的任何错误或者异常:

HttpRequest.getString(url).then((String result){
  print(result);
}).catchError((e) {
  // 处理或者忽略错误
});


then().catchError()形式是try-catch的异步版本。


重要提示:一定要在then()的结果而不是初始的Future对象上调用catchError()。否则,catchError()只处理初始Future对象计算中的错误,而不是then()中注册的代码里的错误。


链式调用多个异步方法

then()方法会返回Future对象,提供了一种按特定顺序执行多个异步函数的有用的方法。如果在then()里注册的回调函数返回一个Future,then()也会返回这个同样的Future。如果这个回调函数返回其他的类型,then()会包装这个值返回一个新的Future对象。

Future result = costlyQuery(url);
result
    .then((value) => expensiveWork(value))
    .then((_) => lengthyComputation())
    .then((_) => print('Done'))
    .catchError((exception) {
  //处理异常
});


在上面的代码里,方法按照下面的顺序执行:

  1. costlyQuery()

  2. expensiveWork()

  3. lengthyComputation()


下面是wait版本的同样代码:

try {
  final value = await costlyQuery(url);
  await expensiveWork(value);
  await lengthyComputation();
  print('Done!');
catch (e) {
  // 处理异常
}


同时等待多个Future结果

有时候你的算法可能会调用多个异步函数,并等待所有的都执行完毕再进行下一步的计算。使用Future.wait()静态方法来处理这种情况:

Future deleteLotsOfFiles() async => ...
Future copyLotsOfFiles() async => ...
Future checksumLotsOfFiles() async => ...

await Future.wait([
  deleteLotsOfFiles(),
  copyLotsOfFiles(),
  checksumLotsOfFiles(),
]);

print('Done with all the long steps!');


Stream

Stream对象代表着一个数据的序列。例如,HTML的事件就像按钮点击事件是通过Stream传递的。文件也可以被读取为Stream。


在循环中使用异步

有时候你可以使用异步循环(await for)来替代使用Stream的接口。

考虑下面的函数,它使用Stream的listen()方法来注册一个文件列表,传递一个搜索文件或目录的函数字面量。

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


使用await表达式的同样代码,包括一个异步循环(await for),看起来更像是同步代码:

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


重要提示:在使用await for之前,确保它能使代码更清晰,你确实需要等待所有stream的结果。例如,await for不能在DOM事件监听器里使用,因为DOM会不停地发送事件。如果使用了await for在一行里注册了两个DOM事件的监听器,第二个永远不会被处理。


监听Stream数据

要在每一个值生成的时候获取到这个值,使用await for或者使用listen()注册对这个stream的监听:

// 找到一个按钮的id并添加一个事件处理器
querySelector('submitInfo').onClick.listen((e) {
  submitData();
});


在这个例子中,onClick属性是'submitInfo'按钮提供的Stream对象。

如果仅仅关心一个事件,可以使用属性first, last或者single来获取它。要在处理它之前进行检查的话,使用方法firstWhere(),lastWhere()或者singleWhere()。

如果关心的是事件序列的子集,可以使用方法skip(),skipWhile(),take(),takeWhile()和where()。


转换Stream数据

通常,在使用stream数据之前,需要对它的格式进行转换。使用transform()方法使用stream数据来生成不同类型的数据:

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


这个例子使用了两个转换器。首先它使用utf8.decoder把整数类型的stream数据转换成字符串类型的stream。然后它使用一个LineSplitter把字符串stream转换为独立的行组成的stream。这些转换器定义在dart:convert库中。


处理错误和代码清理

如何处理错误和进行代码清理依赖于你是否使用了异步for循环(await for)或者Stream的接口。

如果使用了异步for循环,使用try-catch来处理错误。在Stream关闭后执行的代码写在异步for循环后边。

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

  var lines = inputStream
      .transform(utf8.decoder)
      .transform(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监听器来处理错误。并且注册一个onDone()监听器来执行Stream关闭后的代码。

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

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


更多的关于使用Future和Stream的信息,可以参考dart:io相关文档。


dart:math - 数学计算和随机数


dart:math库提供了一些通用的数学计算的功能,例如求正弦、余弦、最大值、最小值,还定义了一些数学常量,如圆周率pi,还有e。大多数这些功能函数在数学库里都被实现为顶层全局函数。

要在你的应用里使用这个库,导入dart:math:

import 'dart:math';


三角运算

// 余弦
assert(cos(pi) == -1.0);

// 正弦
var degree = 30;
var redians = degree * (pi / 180);
var sinOf30degree = sin(radians);
// sin 30 = 0.5
assert((sinOf30degree - 0.5).abs() < 0.01);


注意:这些函数的参数是弧度,而不是角度。


求最大值和最小值

Math库提供了max()和min()方法:

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


数学常量

在Math库里可以找你最喜欢的常量 - pi,e等

print(e); // 2.718281828459045
print(pi); // 3.141592653589793
print(sqrt2); // 1.4142135623730951


随机数

Random类用来生成随机数。可以在Random的构造函数里可选地使用一个种子(seed)

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或者false


dart:convert - JSON, UTF-8等的编解码


dart:convert库定义了JSON和UTF-8的转化器,同时也支持创建其他的转化器。JSON是一种表示结构化对象和集合的非常简单的文字格式。UTF-8是一种常用的可以表示Unicode字符表里每一个字符的可变宽度的编码。

dart:convert库在web应用和命令行应用里都可以使用,要使用它,导入dart:covert库:

import 'dart:convert';


JSON的编解码

使用jsonDecode()函数来将一个JSON格式的字符串转为一个Dart对象:

// 注意,在json字符串里,要使用双引号而不是单引号
// 这个字符串是json,而不是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对象会被递归转换。

不能直接转换的对象有两种方式来进行转换。第一种是在调用encode()时传入第二个参数 :一个可以返回可以直接转换的对象的函数。第二种方式是忽略第二个参数,编码器会调用对象的toJson()方法。


更多的关于JSON相关包的例子和链接,可以参考JSON支持官方文档。


UTF-8字符的编解码

使用utf8.decode()方法来把UTF-8编码的的字节码解码为一个Dart的字符串:

List<int> utf8Bytes = [
  0xc30x8e0xc30xb10xc50xa30xc30xa9,
  0x720xc30xb10xc30xa50xc50xa30xc3,
  0xae0xc30xb60xc30xb10xc30xa50xc4,
  0xbc0xc30xae0xc50xbe0xc30xa50xc5,
  0xa30xc30xae0xe10xbb0x9d0xc30xb1
];

var funnyWord = utf8.decode(utf8Bytes);

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


要把一个UTF-8字符组成的Stream转换为一个Dart字符串,在Stream的transform()方法中指定utf8.decoder。

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

try {
  await for (var line in lines) {
    print('Got ${line.length} characters from stream.');
  }
  print('file is now closed');
catch (e) {
  print(e);
}


使用utf8.encode()方法来把一个Dart字符串编码为一个UTF-8格式字节列表:

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)的转化器。具体可以参考dart:covert库的官方接口规范。


总结


这篇文章介绍了Dart内置库里最常用的一些功能。它并没有覆盖所有的内置库。其他的你可能感兴趣的有:dart:collection和dart:typed_data,还有一些平台相关的库如Dart web开发库和Flutter库。

你还可以使用pub tool来获取更多的库。使用pub你可以安装的库有collection、crypto、http、intl和test等。

要学习更多的关于Dart语言的知识,可以参考Dart语言入门类文章:








本文主要内容翻译自Dart官方英文文档:https://www.dartlang.org/guides/libraries/library-tour





以上是关于Dart语言内置库介绍的主要内容,如果未能解决你的问题,请参考以下文章

『Flutter开发实战』十分钟入门Dart语言

Dart入门语言特性库异步编程

Dart 语言基础入门 Dart 语言核心库一览

Dart 语言基础入门 Dart 语言核心库一览

『Flutter开发实战』一小时掌握Dart语言

Dart语言入门四