dart:async 程式庫

dart:async 提供 Dart 非同步程式設計的核心功能:Future、Stream

  1. 使用 Future / Stream 不一定需要手動匯入 dart:async,dart:core 已經匯出這些類型;
  2. 若要使用 StreamControllerStreamSubscriptionParallelWaitError 等型別,就必須明確匯入:
import 'dart:async';Code language: JavaScript (javascript)

兩大核心觀念:

  • Future:代表單次非同步結果(在未來某個時間點只會產生一個值、或是一次錯誤)
  • Stream:代表連續的一系列非同步資料(持續產生多筆數據,類似事件串流)

Future

1. async / await

優先建議使用,可讀性優於 .then() 鏈式呼叫

  • 標記 async 的函式會回傳 Future
  • await 等待 Future 執行完畢,暫停目前程式直到取得結果;
  • 透過 try-catch 攔截非同步例外。
// async 函式回傳 Future
Future<String> findEntryPoint() async => "main.dart";
Future<int> runExecutable(String entry, List args) async => 0;
Future<void> flushThenExit(int code) async {}

// await 寫法(推薦)
Future<void> runUsingAsyncAwait() async {
  try {
    var entryPoint = await findEntryPoint();
    var exitCode = await runExecutable(entryPoint, []);
    await flushThenExit(exitCode);
  } catch (e) {
    // 統一攔截例外
    print("發生錯誤:$e");
  }
}Code language: JavaScript (javascript)

2. .then() / .catchError() 傳統鏈式寫法

  • .then():Future 成功完成後執行回呼函式;會回傳新的 Future,可持續鏈式呼叫
  • .catchError():捕捉例外;必須接在 then 的回傳值後面鏈接,否則無法捕獲 then 內部發生的錯誤
void runUsingFuture() {
  findEntryPoint()
      .then((entryPoint) {
        return runExecutable(entryPoint, []);
      })
      .then(flushThenExit)
      .catchError((e) {
        print("例外:$e");
      });
}Code language: JavaScript (javascript)

3. 等待多個並行 Future

① Future.wait()

等待所有任務執行完成;只要任一 Future 發生錯誤,整體操作會直接失敗

Future<void> task1() async {}
Future<void> task2() async {}
Future<void> task3() async {}

void testWait() async {
  await Future.wait([
    task1(),
    task2(),
    task3(),
  ]);
  print("所有任務執行完畢");
}Code language: JavaScript (javascript)
② List.wait / Record.wait(新功能,精細處理並行錯誤)

會拋出 ParallelWaitError,能夠分辨哪些任務成功、哪些失敗,任務之間不會互相中斷。

Future<int> delete() async => 1;
Future<String> copy() async => "ok";
Future<bool> errorTask() async {
  throw Exception("任務失敗");
}

void main() async {
  try {
    // 清單形式
    var results = await [delete(), copy(), errorTask()].wait;
  } on ParallelWaitError<List<Object?>, List<AsyncError?>> catch (e) {
    print(e.values);   // 成功任務帶有數值,失敗則為null
    print(e.errors);   // 成功任務為null,失敗存放錯誤資訊
  }

  // Record元組形式:支援不同型別的Future
  try {
    final (resInt, resStr, resBool) = await (delete(), copy(), errorTask()).wait;
  } on ParallelWaitError<(int?, String?, bool?), (AsyncError?, AsyncError?, AsyncError?)> catch(e) {

  }
}Code language: JavaScript (javascript)

Stream

Stream 代表一連串連續的非同步資料(檔案逐行讀取、按鈕點擊事件、網路資料串流等場景)。

兩種使用方式

1:await for 非同步for迴圈(語法簡潔,類似同步走訪)

適合有限數據串流;不適用無限持續事件串流(例如頁面點擊事件),會阻擋後續程式執行。

import 'dart:io';

Future<void> readFileByAwaitFor() async {
  var file = File("config.txt");
  Stream<List<int>> input = file.openRead();
  var lines = input
      .transform(utf8.decoder)
      .transform(const LineSplitter());

  try {
    await for (final line in lines) {
      print("讀取一行:$line");
    }
    print("檔案讀取完畢");
  } catch (e) {
    print("讀取發生錯誤 $e");
  }
}Code language: PHP (php)
2:.listen() 訂閱監聽(適合無限事件串流)
inputStream.listen(
  (data) {
    // 收到資料時執行回呼
  },
  onError: (err) {
    // 發生錯誤時觸發
  },
  onDone: () {
    // 串流正常關閉完成
  },
);Code language: JavaScript (javascript)

listen 會回傳 StreamSubscription,可以呼叫 .cancel() 取消訂閱。

常用 Stream 操作方法

  • 篩選:where()
  • 截取:take()takeWhile()
  • 跳過:skip()skipWhile()
  • 取得單一元素:firstlastsinglefirstWhere()

Stream 資料轉換 transform()

透過轉換器更改串流的資料型別(典型場景:二進位串流轉文字字串)

var lines = inputStream
    .transform(utf8.decoder)       // List<int> → String
    .transform(const LineSplitter()); // String → 逐行文字Code language: JavaScript (javascript)

listen() 與 await for 選擇建議

  1. 檔案讀取、有限序列資料 → await for
  2. UI事件、持續推送訊息等無限串流.listen()
  3. await for 啟動後會持續等待串流結束;無限事件串流將造成程式卡住

dart:async 程式庫

Previous:

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *