首頁 > 軟體

Dart 非同步程式設計生成器及自定義型別用法詳解

2022-11-03 14:02:14

非同步支援

今天介紹一下 Dart 學習的最後一節內容,包括非同步的使用、生成器語法以及型別別名的使用。

Dart 類庫有非常多的返回Future或者Stream物件的函數。 這些函數被稱為非同步函數:它們只會在設定好一些耗時操作之後返回,比如像 IO 操作。而不是等到這個操作完成。

同時,asyncawait關鍵詞支援了非同步程式設計,允許您寫出和同步程式碼很像的非同步程式碼。

Future

Future與 JavaScript 中的Promise非常相似,表示一個非同步操作的最終完成(或失敗)及其結果值的表示。簡單來說,它就是用於處理非同步操作的,非同步處理成功了就執行成功的操作,非同步處理失敗了就捕獲錯誤或者停止後續操作。一個Future 只會對應一個結果,要麼成功,要麼失敗。

注: Future 的所有API的返回值仍然是一個Future物件,所以可以很方便的進行鏈式呼叫。

Future.then

為了方便範例,在本例中使用Future.delayed 建立了一個延時任務(實際場景會是一個真正的耗時任務,比如一次網路請求),即2秒後返回結果字串hi world!,然後我們在then中接收非同步結果並列印結果,程式碼如下:

 Future.delayed(new Duration(seconds: 2),(){
    return "hi world!";
 }).then((data){
    print(data);
 });

Future.catchError

如果非同步任務發生錯誤,可以在catchError中捕獲錯誤,將上面範例改為:

 Future.delayed(new Duration(seconds: 2),(){
    //return "hi world!";
    throw AssertionError("Error");  
 }).then((data){
    //執行成功會走到這裡  
    print("success");
 }).catchError((e){
    //執行失敗會走到這裡  
    print(e);
 });

then方法還有一個可選引數onError,也可以它來捕獲異常:

 Future.delayed(new Duration(seconds: 2), () {
     //return "hi world!";
     throw AssertionError("Error");
 }).then((data) {
     print("success");
 }, onError: (e) {
     print(e);
 });

Future.whenComplete

有些時候,我們會遇到無論非同步任務執行成功或失敗都需要做一些事的場景,比如在網路請求前彈出載入對話方塊,在請求結束後關閉對話方塊。這種場景,有兩種方法,第一種是分別在thencatch中關閉一下對話方塊,第二種就是使用FuturewhenComplete回撥,我們將上面範例改一下:

Future.delayed(new Duration(seconds: 2),(){
   //return "hi world!";
   throw AssertionError("Error");
}).then((data){
   //執行成功會走到這裡 
   print(data);
}).catchError((e){
   //執行失敗會走到這裡   
   print(e);
}).whenComplete((){
   //無論成功或失敗都會走到這裡
});

Future.wait

使用Future.wait可以做到多個Future同時出發才會進行後續操作,同 JavaScript 中的Promise.all()方法。

Future.wait接受一個Future陣列引數,只有陣列中所有Future都執行成功後,才會觸發then的成功回撥,只要有一個Future執行失敗,就會觸發錯誤回撥。下面,我們通過模擬Future.delayed 來模擬兩個資料獲取的非同步任務,等兩個非同步任務都執行成功時,將兩個非同步任務的結果拼接列印出來,程式碼如下:

Future.wait([
  // 2秒後返回結果  
  Future.delayed(new Duration(seconds: 2), () {
    return "hello";
  }),
  // 4秒後返回結果  
  Future.delayed(new Duration(seconds: 4), () {
    return " world";
  })
]).then((results){
  print(results[0]+results[1]);
}).catchError((e){
  print(e);
});
/*
	最後會在4秒後拿到結果
*/

更多 Future 的 api 請自行查詢檔案。

async 和 await

非同步函數是函數體被用async修飾符標記的函數。 向函數中新增async關鍵字將使其返回一個 Future。

String lookUpVersion() => '1.0.0'; // 返回String
Future<String> lookUpVersion() async => '1.0.0'; // 返回Future<String>

然後我們可以使用await關鍵字在內部直接接受一個 Future 的then的成功回撥,就如同 JavaScript 中的那樣:

task() async {
    try{
        String id = await login("alice","******");
        String userInfo = await getUserInfo(id);
        await saveUserInfo(userInfo);
        // 執行接下來的操作   
    } catch(e){
        // 錯誤處理   
        print(e);   
    }  
}
  • async用來表示函數是非同步的,定義的函數會返回一個Future物件,可以使用then方法新增回撥函數。
  • await 後面是一個Future,表示等待該非同步任務完成,非同步完成後才會往下走。注意,await必須出現在 async 函數內部。

注意: 函數的主體不需要使用 Future 的 API。如果需要,Dart 將建立 Future 的物件。如果沒有返回一個有用的值,那麼將其返回Future<void>型別。

處理流(Stream)

Stream 也是用於接收非同步事件資料,和Future 不同的是,它可以接收多個非同步操作的結果(成功或失敗)。 也就是說,在執行非同步任務時,可以通過多次觸發成功或失敗事件來傳遞結果資料或錯誤異常。 Stream 常用於會多次讀取資料的非同步任務場景,如網路內容下載、檔案讀寫等。

當需要從 Stream 獲取值時,有兩個選擇:

使用async和非同步的for迴圈(await for

注: 在使用await for之前,請確保它使程式碼更清晰,並且確實希望等待流(Stream)的所有結果。例如,通常不應該為 UI 事件使用await,因為 UI 框架會傳送無窮無盡的事件流。

非同步for迴圈有以下形式:

 await for (varOrType identifier in expression) {
   // Executes each time the stream emits a value.
 }

表示式的值必須具有 Stream 型別。執行過程如下:

等待流發出值。

執行for迴圈的主體,並將變數設定為發出的值。

重複1和2,直到流關閉。

要停止偵聽流,可以使用breakreturn語句,該語句將跳出for迴圈,並從流中取消訂閱。

如果在實現非同步for迴圈時出現編譯時錯誤,請確保await在非同步函數中。例如,要在應用程式的main()函數中使用非同步for迴圈,main()的主體必須標記為async

 Future main() async {
   // ...
   await for (var request in requestServer) {
     handleRequest(request);
   }
   // ...
 }

使用Stream API,如[庫的引導]中的描述

 Stream.fromFutures([
   // 1秒後返回結果
   Future.delayed(new Duration(seconds: 1), () {
     return "hello 1";
   }),
   // 丟擲一個異常
   Future.delayed(new Duration(seconds: 2),(){
     throw AssertionError("Error");
   }),
   // 3秒後返回結果
   Future.delayed(new Duration(seconds: 3), () {
     return "hello 3";
   })
 ]).listen((data){
    print(data);
 }, onError: (e){
    print(e.message);
 }, onDone: (){
 
 });
 /*
 上面的程式碼依次會輸出:
 I/flutter (17666): hello 1
 I/flutter (17666): Error
 I/flutter (17666): hello 3
 */

生成器

當需要延遲地生成一個值序列時,請考慮使用生成器函數。

Dart 內建支援兩種生成器函數:

  • 同步生成器:返回 Iterable 物件
  • 非同步生成器:返回 Stream 物件

要實現同步生成器函數,將函數體標記為sync*,並使用yield語句傳遞值:

Iterable<int> naturalsTo(int n) sync* {
  int k = 0;
  while (k < n) yield k++;
}

要實現非同步生成器函數,將函數體標記為async*,並使用yield語句傳遞值:

Stream<int> asynchronousNaturalsTo(int n) async* {
  int k = 0;
  while (k < n) yield k++;
}

如果生成器是遞迴的,可以使用yield*來改進它的效能:

Iterable<int> naturalsDownFrom(int n) sync* {
  if (n > 0) {
    yield n;
    yield* naturalsDownFrom(n - 1);
  }
}

型別定義

在 Dart 中,函數是物件,就像字串和數位是物件一樣。typedeffunction-type為函數提供一個型別別名,可以在宣告欄位和返回型別時使用這個名稱。當函數型別被分配給變數時,typedef保留型別資訊。

以下程式碼不使用typedef

class SortedCollection {
  Function compare;
  SortedCollection(int f(Object a, Object b)) {
    compare = f;
  }
}
// Initial, broken implementation.
int sort(Object a, Object b) => 0;
void main() {
  SortedCollection coll = SortedCollection(sort);
  // All we know is that compare is a function,
  // but what type of function?
  assert(coll.compare is Function);
}

上面的程式碼中,當給compare分配f時型別資訊會丟失。f的型別是(Object, Object)->int(int表示返回值型別),當然,compare的型別是Function。如果我們更改程式碼以使用顯式名稱和保留型別資訊,開發人員和工具都可以使用這些資訊。

typedef Compare = int Function(Object a, Object b);
class SortedCollection {
  Compare compare;
  SortedCollection(this.compare);
}
// Initial, broken implementation.
int sort(Object a, Object b) => 0;
void main() {
  SortedCollection coll = SortedCollection(sort);
  assert(coll.compare is Function);
  assert(coll.compare is Compare);
}

注意: 目前,typedefs僅限於函數型別,可能在之後會有所改變。

因為typedef僅僅是別名,所以它們提供了一種檢查任何函數型別的方法。例如:

typedef Compare<T> = int Function(T a, T b);
int sort(int a, int b) => a - b;
void main() {
  assert(sort is Compare<int>); // True!
}

後設資料

使用後設資料提供關於程式碼的附加資訊。後設資料註釋以字元@開頭,後跟對編譯時常數(如deprecated)的參照或對常數建構函式的呼叫。

所有 dart 程式碼都可以使用兩個註釋:@deprecated(棄用註釋)和@override。這裡有一個使用@deprecated註釋的例子:

class Television {
  /// _Deprecated: Use [turnOn] instead._
  @deprecated
  void activate() {
    turnOn();
  }
  /// Turns the TV's power on.
  void turnOn() {...}
}

可以定義自己的後設資料註釋(也就是類似 JavaScript 中的裝飾器的效果)。這裡有一個定義帶有兩個引數的@todo註釋的範例:

 library todo;
 class Todo {
   final String who;
   final String what;
   const Todo(this.who, this.what);
 }

這裡有一個使用@todo註釋的例子:

 import 'todo.dart';
 
 @Todo('seth', 'make this do something')
 void doSomething() {
   print('do something');
 }

後設資料可以出現在庫、類、型別定義、型別引數、建構函式、工廠、函數、欄位、引數或變數宣告之前,也可以出現在匯入或匯出指令之前。可以使用反射在執行時檢索後設資料。

核心庫的使用

可以參考官方檔案中的介紹:A tour of the core libraries

以上就是Dart 非同步程式設計生成器及自定義型別用法詳解的詳細內容,更多關於Dart 非同步程式設計生成器的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com