Dart 有一些語言特性來支持 異步編程试躏。 最常見的特性是
async
方法和await
表達(dá)式。Dart 庫中有很多返回 Future 或者 Stream 對(duì)象的方法棠枉。 這些方法是 異步的浓体, 這些函數(shù)在設(shè)置完基本的操作后就返回了, 而無需等待操作執(zhí)行完成辈讶。 例如讀取一個(gè)文件命浴,在打開文件后就返回了。
Future 和 JavaScript 中的 Promise 類似贱除,代表在將來某個(gè)時(shí)刻會(huì)返回一個(gè) 結(jié)果生闲。Stream 是一種用來獲取一些列數(shù)據(jù)的方式,例如 事件流月幌。 Future, Stream, 以及其他異步操作的類在 dart:async 庫中碍讯。
dart:async 庫在 web app 和命令行 app 都可以使用。 只需要導(dǎo)入 dart:async 即可使用:
import 'dart:async';
一扯躺、Future
有兩種方式可以使用 Future 對(duì)象中的 數(shù)據(jù):
- 使用
async
和await
(要使用await
捉兴,其方法必須帶有async
關(guān)鍵字) - 使用 Future API
- 使用
await
的表達(dá)式比直接使用 Future api 的代碼要更加容易理解。
在 Dart 庫中隨處可見 Future 對(duì)象录语,通常異步函數(shù)返回的對(duì)象就是一個(gè) Future倍啥。 當(dāng)一個(gè) future 執(zhí)行完后,他里面的值 就可以使用了澎埠。
checkVersion() async {
var version = await lookUpVersion();
if (version == expectedVersion) {
// Do something.
} else {
// Do something else.
}
}
可以使用 try
, catch
, 和 finally
來處理使用 await
的異常:
try {
server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 4044);
} catch (e) {
// 無法綁定到端口時(shí)作出反應(yīng)...
}
1.1.Declaring async functions(聲明異步方法)
async
方法: 是函數(shù)體被標(biāo)記為 async
的方法虽缕。 雖然異步方法的執(zhí)行可能需要一定時(shí)間,但是異步方法立刻返回 (在方法體還沒執(zhí)行之前就返回了)蒲稳。
checkVersion() async {
// ...
}
lookUpVersion() async => /* ... */;
在一個(gè)方法上添加 async
關(guān)鍵字氮趋,則這個(gè)方法返回值為 Future。 例如江耀,下面是一個(gè)返回字符串 的同步方法:
String lookUpVersionSync() => '1.0.0';
如果使用 async
關(guān)鍵字凭峡,則該方法 返回一個(gè) Future,并且 認(rèn)為該函數(shù)是一個(gè)耗時(shí)的操作决记。
Future<String> lookUpVersion() async => '1.0.0';
注意:方法的函數(shù)體并不需要使用 Future API。 Dart 會(huì)自動(dòng)在需要的時(shí)候創(chuàng)建 Future 對(duì)象倍踪。
1.2.Using await expressions with Futures(使用 await 表達(dá)式)
await
表達(dá)式具有如下的形式:
await expression
在一個(gè)異步方法內(nèi)可以使用多次 await
表達(dá)式系宫。 例如索昂,下面的示例使用了三次 await
表達(dá)式 來執(zhí)行相關(guān)的功能:
var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);
在 await expression
中, expression 的返回值通常是一個(gè) Future扩借; 如果返回的值不是 Future椒惨,則 Dart 會(huì)自動(dòng)把該值放到 Future 中返回。 Future 對(duì)象代表返回一個(gè)對(duì)象的 promise 潮罪。 await expression
執(zhí)行的結(jié)果為這個(gè)返回的對(duì)象康谆。 await expression
會(huì)阻塞住,直到需要的對(duì)象返回為止嫉到。
如果 await
無法正常使用沃暗,請(qǐng)確保是在一個(gè) async
方法中。 例如要在 main()
方法中使用 await
何恶, 則 main()
方法的函數(shù)體必須標(biāo)記為 async:
main() async {
checkVersion();
print('In main: version is ${await lookUpVersion()}');
}
1.3.Future 的API
使用 Future 的 then()
函數(shù)來 執(zhí)行三個(gè)異步方法孽锥, 每個(gè)方法執(zhí)行完后才繼續(xù)執(zhí)行后一個(gè)方法。
runUsingFuture() {
//...
findEntrypoint().then((entrypoint) {
return runExecutable(entrypoint, args);
}).then(flushThenExit);
}
下面是使用 await
表達(dá)式實(shí)現(xiàn)的同樣功能的代碼细层, 看起來更像是同步代碼惜辑,更加容易理解:
runUsingAsyncAwait() async {
//...
var entrypoint = await findEntrypoint();
var exitCode = await runExecutable(entrypoint, args);
await flushThenExit(exitCode);
}
可以使用 then()
來在 future 完成的時(shí)候執(zhí)行其他代碼。例如 HttpRequest.getString()
返回一個(gè)Future疫赎,由于 HTTP 請(qǐng)求是一個(gè) 耗時(shí)操作盛撑。使用then()
可以在 Future 完成的時(shí)候執(zhí)行其他代碼 來解析返回的數(shù)據(jù):
HttpRequest.getString(url).then((String result) {
print(result);
});
// Should handle errors here.
使用 catchError()
來處理 Future 對(duì)象可能拋出的 各種異常和錯(cuò)誤:
HttpRequest.getString(url).then((String result) {
print(result);
}).catchError((e) {
// Handle or ignore the error.
});
then().catchError()
模式就是異步版本的 try
-catch
。
重要: 確保是在
then()
返回的 Future 上調(diào)用catchError()
捧搞, 而不是在 原來的 Future 對(duì)象上調(diào)用抵卫。否則的話,catchError()
就只能處理原來 Future 對(duì)象拋出的異常而無法處理then()
代碼 里面的異常实牡。
1.4.Chaining multiple asynchronous methods(鏈接多個(gè)異步方法)
then() 函數(shù)返回值為 Future陌僵,可以把多個(gè)異步調(diào)用給串聯(lián)起來。 如果 then() 函數(shù)注冊(cè)的回調(diào)函數(shù)也返回一個(gè) Future创坞,而 then() 返回一個(gè)同樣的 Future碗短。如果回調(diào)函數(shù)返回的是一個(gè)其他類型的值, 則 then() 會(huì)創(chuàng)建一個(gè)新的 Future 對(duì)象 并完成這個(gè) future题涨。
Future result = costlyQuery();
return result.then((value) => expensiveWork())
.then((value) => lengthyComputation())
.then((value) => print('done!'))
.catchError((exception) => print('DOH!'));
上面的示例中偎谁,代碼是按照如下順序執(zhí)行的:
costlyQuery()
expensiveWork()
lengthyComputation()
1.5.Waiting for multiple futures(等待多個(gè)future)
有時(shí)候,你的算法要求調(diào)用很多異步方法纲堵,并且等待 所有方法完成后再繼續(xù)執(zhí)行巡雨。使用 Future.wait()
這個(gè)靜態(tài)函數(shù)來管理多個(gè) Future 并等待所有 Future 執(zhí)行完成。
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 中也經(jīng)常出現(xiàn)铐望,代表一些列數(shù)據(jù)。 例如, HTML 按鈕點(diǎn)擊事件就可以使用 stream 來表示正蛙。 還可以把讀取文件內(nèi)容當(dāng)做一個(gè) Stream督弓。
從 Stream 中獲取數(shù)據(jù)兩種方式:
- 使用
async
和一個(gè) 異步 for 循環(huán) (await for
) - 使用 Stream API
2.1.Using asynchronous for loops with Streams(在循環(huán)中使用異步)
異步 for 循環(huán)具有如下的形式:
await for (variable declaration in expression) {
// Executes each time the stream emits a value.
}
上面 expression
返回的值必須是 Stream 類型的。 執(zhí)行流程如下:
- 等待直到 stream 返回一個(gè)數(shù)據(jù)
- 使用 stream 返回的參數(shù) 執(zhí)行
for
循環(huán)代碼乒验, - 重復(fù)執(zhí)行 1 和 2 直到 stream 數(shù)據(jù)返回完畢愚隧。
- 使用
break
或者return
語句可以 停止接收 stream 的數(shù)據(jù), 這樣就跳出了for
循環(huán)并且 從 stream 上取消注冊(cè)了锻全。
如果異步 for
循環(huán)不能正常工作狂塘, 確保是在一個(gè) async 方法中使用。 例如鳄厌,要想在 main()
方法中使用異步 for
循環(huán)荞胡,則需要把 main()
方法的函數(shù)體標(biāo)記為 async:
main() async {
...
await for (var request in requestServer) {
handleRequest(request);
}
...
}
2.2.Using an asynchronous for loop(for循環(huán)的異步使用)
有時(shí)候可以使用異步 for
循環(huán)(await for
)來 替代 Stream API。
例如下面的示例中部翘,使用 Stream 的 listen()
函數(shù)來訂閱 一些文件硝训,然后使用一個(gè)方法參數(shù)來 搜索每個(gè)文件和目錄。
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
表達(dá)式和異步 for
循環(huán) 實(shí)現(xiàn)的等價(jià)的代碼新思, 看起來更像是同步代碼:
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
之前請(qǐng)確保使用之后的代碼看起來確實(shí)是 更加清晰易懂了窖梁。例如,對(duì)于 DOM時(shí)間監(jiān)聽器通常 不會(huì)使用await for
夹囚,原因在于 DOM 發(fā)送 無盡的事件流纵刘。 如果使用 await for 來在同一行注冊(cè)兩個(gè) DOM 事件監(jiān)聽器, 則第二個(gè)事件在不會(huì)被處理荸哟。
2.3.Listening for stream data(監(jiān)聽stream數(shù)據(jù))
要想在每個(gè)數(shù)據(jù)到達(dá)的時(shí)候就去處理假哎,則可以選擇使用 await for
或者 使用 listen()
函數(shù)來訂閱事件:
// 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” 按鈕提供的一個(gè) Stream 對(duì)象鞍历。
如果你只關(guān)心里面其中一個(gè)事件舵抹,則可以使用這些屬性: first
, last
, 或者 single
。要想在處理事件之前先測(cè)試是否滿足條件劣砍,則 使用 firstWhere()
, lastWhere()
, 或者 singleWhere()
函數(shù)惧蛹。
如果你關(guān)心一部分事件,則可以使用 skip()
, skipWhile()
, take()
, takeWhile()
, 和 where()
這些函數(shù)刑枝。
2.4.Transforming stream data(轉(zhuǎn)換stream數(shù)據(jù))
經(jīng)常你需要先轉(zhuǎn)換 stream 里面的數(shù)據(jù)才能使用香嗓。 使用 transform()
函數(shù)可以生產(chǎn)另外一個(gè)數(shù)據(jù)類型 的 Stream 對(duì)象:
var stream = inputStream
.transform(UTF8.decoder)
.transform(new LineSplitter());
上面的代碼使用兩種轉(zhuǎn)換器(transformer)。第一個(gè)使用 UTF8.decoder 來把整數(shù)類型的數(shù)據(jù)流轉(zhuǎn)換為字符串類型的數(shù)據(jù)流装畅。然后使用 LineSplitter 把字符串類型數(shù)據(jù)流轉(zhuǎn)換為按行分割的數(shù)據(jù)流靠娱。 這些轉(zhuǎn)換器都來至于 dart:convert 庫。 參考 dart:convert ) 了解詳情掠兄。
2.5.Handling errors and completion(處理錯(cuò)誤和完善)
使用異步 for
循環(huán) (await for
) 和使用 Stream API 的 異常處理情況是有 區(qū)別的像云。
如果是異步 for
循環(huán)锌雀,則可以 使用 try
-catch
來處理異常。
在 stream 關(guān)閉后執(zhí)行的代碼位于異步 for
循環(huán) 之后苫费。
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 函數(shù)來處理異常。 stream 完成后執(zhí)行的代碼要通過 onDone 函數(shù) 來執(zhí)行百框。
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);
});
2.6.More information(更多信息)
關(guān)于在命令行應(yīng)用中使用 Future 和 Stream 的更多示例,請(qǐng)參考 dart:io 里面的內(nèi)容牍汹。 下面也是一些可以參考的文章和教程:
三铐维、Isolates
現(xiàn)代的瀏覽器以及移動(dòng)瀏覽器都運(yùn)行在多核 CPU 系統(tǒng)上。 要充分利用這些 CPU慎菲,開發(fā)者一般使用共享內(nèi)存數(shù)據(jù)來保證多線程的正確執(zhí)行嫁蛇。然而, 多線程共享數(shù)據(jù)通常會(huì)導(dǎo)致很多潛在的問題露该,并導(dǎo)致代碼運(yùn)行出錯(cuò)睬棚。
所有的 Dart 代碼在 isolates 中運(yùn)行而不是線程。 每個(gè) isolate 都有自己的堆內(nèi)存解幼,并且確保每個(gè) isolate 的狀態(tài)都不能被其他 isolate 訪問抑党。
待續(xù)。撵摆。底靠。