2019年8月18日 星期日

Dart 數列及資料流產生器, async/await/Future

參考 https://www.youtube.com/watch?v=TF-TBsgIErY
在 dart 世界裡, 透過 sync* 關鍵字標註一個函式讓它可以用 yield 暫停後回傳一筆資料(實際上是包裝成一個 iterable 物件直接回傳,因此不會 block 程式),讓後續用 forEach 依序讀取, 若將 yield 放進一個 for 迴圈, 就能產生一個數列:
        IntRange(int start, int end) sync* {for(int i=start; i<=end; i++)  yield i;}//一個整數數列
        void main() => IntRange(1,10).forEach(print); // 實際上是一個  iterable物件
或是搭配 yield * 直接呼叫遞迴函式(recursive funtion), 讓它看似沒有 loop 的序列產生器:
     IntRange(int start, int end) sync* { // 打包成 Iterable 類型的物件
         if (start <= end) { // 結束遞迴的條件
              yield start;      // 產生初始值
              yield * IntRange(start+1, end); // 事件(event loop)註冊, 下次繼續用遞迴函式產生
         }
     }
     void main() => IntRange(1,10).forEach(print); // 實際上是一個  iterable物件
當來到非同步世界後, 同樣可以用 yield 產生序列, 但要改用 async* (非同步產生器)標註函式, async * 所產生的是序列會包裝成資料流 stream, 而非 iterable:
     IntStrean(int start, int end) async* {for(int i=start; i<=end; i++)  yield i;}//一串整數流
     void main() => IntStrean(1,10).forEach(print);//實際上是一個 Stream<int> 物件
若是 yieild Future 物件的話, 也就是非同步串, 實際上 Future 是一個抽象類型(abstract class), 它並非當下產生資料, 而是當完成後 then( )或是等待他完成 await 才現出物件的原型:
     futureStream(int start, int end)  async * {  for(int i=start; i <=end; i++) yield     Future.value(i); } // 實際是回傳 Stream<Future>
     void main()  =>  futureStream(1,10).forEach( (f) => f.then(print));// f 實際上是 Future 物件, 用 then( ) 現出物件原型

用 yield await 看似等待完成後才回傳, 實際只是註冊到 event loop , 順便將物件打包成相對應之類型,他並不會 block 程式的運行:
     intStream(int start, int end)  async* { // 打包成 Stream 類型的物件
         for(int i=start; i <=end; i++) yield await Future.value(i); // 註冊至 event loop, 用 await 現出原型
     }
     void main() => intStream(1,10).forEach(print);//實際上是一個 Stream<int>物件
當然也能搭配 yield * 呼叫遞迴函式(recursive funtion)形成一個看似不用 loop 的資料流產生器,實際上也是註冊至 event loop, 待後續再執行:
     intStream(int start, int end) async* {
         if (start <= end) { // 結束遞迴的條件
              yield await Future.value(start);  // 註冊事件, 用 await 等待物件現出原型
              yield * intStream(start+1, end); // 只註冊至 event loop 續用遞迴函式, 無需等待
         }
     }
     void main() => intStream(1,10).forEach(print);//實際上是一個 Stream<int>物件
dart 程式庫的 Future 物件可類比成 Javascript 的 promise 物件, 而將 Future.value(值)類比成 promise 的 resolv(非同步傳回值), 他們運作邏輯是相同的, 同樣的,我們也可以用簡單邏輯來理解 async/await 的運作方式,  async 其實只是把將來要回傳(return)的值一起打包成 Future/promise 物件, 而 await 負責註冊事件並把後續程式碼(位在相應的 async 函式碼)打包放入 then( ) 的 callback 函式內,後續當物件現出原形時, 呼叫這個 callback  方法完成後續動作, then 是 Future/promise 裏面一個註冊過的從屬函式(簡稱方法), 一旦條件滿足(物件現出原形)才會執行. 對於 sync * 產生器而言, 打包的是 iterable 物件, iterable 物件是一個在迴圈中可以一個一個取出使用的一串數列, 而 async * 所打包的是 Stream 類型的物件, Stream 則是一個可以一個一個取出來的非同步事件(a sequence of events), forEach 只是其中一個取出物件的方法, 透過註冊事件與物件方法間接或直接讀取物件的原型, 而不會 block 程式的運行,如此而已. Dart 的 async/await 語法跟 Javascript 語法長的很像:
     asyncFunction( ) async =>  3; // async 標註會將函式傳回值打包成 Future 類型的物件
     void main( ) async =>  print(await asyncFunction( )); // await 只能在 async 用, 他讓物件現出原型
或是同步程式的寫法,用 Future 的 then( ) 方法現出物件原型後再處理:
     void main( )          =>  asyncFunction( ).then(print);
同步程式也可以打包成 Future 物件, 後續沿用 then 方法取得物件原型:
     syncFunction( )   => Future.value(3);  // Future.value 是立刻賦值, promise resolve
     void main( )         => syncFunction( ).then(print); // then 其實是一個 Future 物件裏面一個方法

沒有留言: